-- allium: 1
-- WordPress WXR Import
-- Scope: extension (saved analysis, conflict review, recoverable execution)
-- Distilled from: ../bDS2/lib/bds/wxr_parser.ex,
--                 ../bDS2/lib/bds/import_analysis.ex,
--                 ../bDS2/lib/bds/import_execution.ex,
--                 ../bDS2/lib/bds/import_definitions.ex,
--                 ../bDS2/lib/bds/desktop/shell_live/import_editor*

use "./project.allium" as project

enum ImportItemStatus { new | update | conflict | content_duplicate | missing }
enum ImportResolution { ignore | overwrite | import }

value ImportedSite {
    title: String
    url: String?
    language: String?
}

value ImportCandidate {
    kind: post | page | media
    source_id: Integer?
    title: String
    slug: String?
    filename: String?
    status: ImportItemStatus
    resolution: ImportResolution?
    existing_id: String?
    author: String?
    excerpt: String?
    content: String?
    source_status: String?
    categories: List<String>
    tags: List<String>
    source_path: String?
    parent_source_id: Integer?
    created_at: Timestamp?
    updated_at: Timestamp?
    published_at: Timestamp?
}

value TaxonomyCandidate {
    kind: category | tag
    name: String
    slug: String?
    exists_in_project: Boolean
    mapped_to: String?
}

value ImportCounts {
    new_count: Integer
    update_count: Integer
    conflict_count: Integer
    duplicate_count: Integer
    missing_count: Integer
}

value ImportDateBucket {
    year: Integer
    post_count: Integer
    media_count: Integer
}

value ImportMacroUsage {
    name: String
    total_count: Integer
    post_slugs: List<String>
}

value ImportReport {
    source_file: String
    uploads_folder: String?
    site: ImportedSite
    posts: List<ImportCandidate>
    pages: List<ImportCandidate>
    media: List<ImportCandidate>
    taxonomies: List<TaxonomyCandidate>
    post_counts: ImportCounts
    page_counts: ImportCounts
    media_counts: ImportCounts
    date_distribution: List<ImportDateBucket>
    macros: List<ImportMacroUsage>
}

entity ImportDefinition {
    project: project/Project
    name: String
    wxr_file_path: String?
    uploads_folder_path: String?
    last_analysis: ImportReport?
    created_at: Timestamp
    updated_at: Timestamp
}

config {
    transaction_batch_size: Integer = 500
}

surface ImportDefinitionSurface {
    context definition: ImportDefinition

    exposes:
        definition.project
        definition.name
        definition.wxr_file_path when definition.wxr_file_path != null
        definition.uploads_folder_path when definition.uploads_folder_path != null
        definition.last_analysis when definition.last_analysis != null
        definition.created_at
        definition.updated_at
}

surface WordPressImportSurface {
    facing _: ImportOperator

    exposes:
        ImportReport
        ImportCandidate
        TaxonomyCandidate

    provides:
        CreateImportDefinitionRequested(project, name)
        RenameImportDefinitionRequested(definition, name)
        SelectWxrFileRequested(definition, file_path)
        SelectUploadsFolderRequested(definition, folder_path)
        AnalyzeWxrRequested(definition)
        ResolveImportConflictRequested(definition, kind, item_name, resolution)
        MapImportTaxonomyRequested(definition, kind, source_name, target_name)
        AutoMapImportTaxonomyRequested(definition, model)
        ExecuteWordPressImportRequested(definition)
        DeleteImportDefinitionRequested(definition)

    @guarantee AnalysisReview
        -- Analysis runs asynchronously and reports localized progress while it
        -- loads project state, analyzes posts, pages and media, processes
        -- taxonomy, and discovers WordPress shortcodes. The saved report shows
        -- item counts, conflicts, missing uploads, year distribution, macro
        -- usage, and importable totals before any project content is changed.
        -- Its Conflicts, Posts, Pages, Media, Taxonomy, and Macros sections
        -- expose unique Control+Option or Control+Alt access keys for disclosure control.

    @guarantee ConflictReview
        -- Conflicts default to ignore. The operator can keep ignoring, overwrite
        -- the existing item, or import a second item with a unique slug.
        -- Same-content updates and duplicates are reported but skipped during
        -- execution, matching bDS2.

    @guarantee TaxonomyReview
        -- Existing terms are matched case-insensitively. New source categories
        -- and tags may be mapped to existing project terms, cleared, or created
        -- as new terms. Free-text category mappings are normalized before save.

    @guarantee AiTaxonomyGate
        -- Optional automatic taxonomy mapping uses the selected configured AI
        -- model. In airplane mode it runs only through a configured local
        -- endpoint; otherwise it is refused with localized guidance. The saved
        -- report is updated only after a successful mapping response.

    @guarantee ExecutionProgress
        -- Execution is unavailable when the report contains no importable
        -- items. While running, the editor shows phase, completed/total count,
        -- current detail and estimated remaining time for taxonomy, posts,
        -- media and pages, followed by a localized result summary or error.
}

rule CreateImportDefinition {
    when: CreateImportDefinitionRequested(project, name)
    ensures: ImportDefinition.created(
        project: project,
        name: name,
        wxr_file_path: null,
        uploads_folder_path: null,
        last_analysis: null,
        created_at: now,
        updated_at: now
    )
}

rule RenameImportDefinition {
    when: RenameImportDefinitionRequested(definition, name)
    ensures: definition.name = name
    ensures: definition.updated_at = now
}

rule SelectWxrFile {
    when: SelectWxrFileRequested(definition, file_path)
    ensures: definition.wxr_file_path = file_path
    ensures: definition.last_analysis = null
    ensures: definition.updated_at = now
}

rule SelectUploadsFolder {
    when: SelectUploadsFolderRequested(definition, folder_path)
    ensures: definition.uploads_folder_path = folder_path
    ensures: definition.updated_at = now
}

rule AnalyzeWxr {
    when: AnalyzeWxrRequested(definition)
    requires: definition.wxr_file_path != null
    let report = analyze_wordpress_export(
        definition.project,
        definition.wxr_file_path,
        definition.uploads_folder_path
    )
    ensures: definition.last_analysis = report
    ensures: definition.updated_at = now
}

rule ResolveImportConflict {
    when: ResolveImportConflictRequested(definition, kind, item_name, resolution)
    requires: definition.last_analysis != null
    requires: resolution = ignore or resolution = overwrite or resolution = import
    ensures: definition.last_analysis = with_conflict_resolution(
        definition.last_analysis,
        kind,
        item_name,
        resolution
    )
    ensures: definition.updated_at = now
}

rule MapImportTaxonomy {
    when: MapImportTaxonomyRequested(definition, kind, source_name, target_name)
    requires: definition.last_analysis != null
    ensures: definition.last_analysis = with_taxonomy_mapping(
        definition.last_analysis,
        kind,
        source_name,
        target_name
    )
    ensures: definition.updated_at = now
}

rule AutoMapImportTaxonomy {
    when: AutoMapImportTaxonomyRequested(definition, model)
    requires: definition.last_analysis != null
    ensures: definition.last_analysis = with_ai_taxonomy_mappings(definition.last_analysis, model)
    ensures: definition.updated_at = now
}

rule ExecuteWordPressImport {
    when: ExecuteWordPressImportRequested(definition)
    requires: definition.last_analysis != null
    requires: importable_count(definition.last_analysis) > 0
    ensures: WordPressImportExecuted(definition, result)

    @guidance
        -- New posts/pages use the source author or project default, preserve
        -- source timestamps, taxonomy and published/draft status, and route
        -- through normal post persistence/publishing. Pages also receive the
        -- page category. Imported media route through normal media import,
        -- preserve description as alt text, and link to an imported parent post
        -- when its WordPress parent is known. Missing media are skipped.
}

rule DeleteImportDefinition {
    when: DeleteImportDefinitionRequested(definition)
    ensures: not exists definition
}

invariant WxrConversionParity {
    -- A valid export requires an RSS channel and extracts site metadata, posts,
    -- pages, attachments, categories and tags. Post HTML is converted to
    -- Markdown and WordPress shortcodes become bDS [[macro ...]] calls.
}

invariant AnalysisClassification {
    -- Post/page checksums are lower-hex SHA-256 of converted Markdown; media
    -- checksums are lower-hex MD5 of source bytes, matching bDS2.
    -- Posts/pages: same slug and checksum => update; same slug with different
    -- content => conflict; checksum matching another post => content duplicate;
    -- otherwise new. Media applies the same rules by original filename and
    -- checksum, with missing selected-upload files classified as missing.
}

invariant CoreEngineReuse {
    -- Execution uses the ordinary tag, post, publishing, media, filesystem,
    -- search, embedding and domain-event paths. It does not create a parallel
    -- import-only data model or bypass normal side effects.
}

invariant RecoverableExecution {
    -- Taxonomy, posts, media and pages execute in that order. Each phase is
    -- committed in batches of config.transaction_batch_size. A failure rolls
    -- back the current batch and stops with a structured error; completed
    -- batches remain available for inspection and a later retry.
}

invariant SafeUntrustedInput {
    -- Unknown XML elements and report keys are ignored without creating
    -- unbounded runtime identifiers. Malformed XML or a missing RSS channel
    -- fails analysis without changing project content. Progress/reporting
    -- callback failures never abort otherwise valid analysis or execution.
}
