import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
/**
 * Promote a Tier-2 draft block from `blocks/_drafts/<slug>.dql` or
 * `domains/<domain>/blocks/_drafts/<slug>.dql` to the canonical block
 * location, flipping `status` to `certified`. When a DataLex contract is
 * supplied, the promoted block is also bound to that contract.
 *
 * Companion to the dql-mcp `query_via_metadata` tool. Together they close
 * the graduated-trust promotion loop:
 *
 *   ask  ->  no Tier-1 hit  ->  query_via_metadata writes a draft
 *        ->  human reviews / refines
 *        ->  dql certify --from-draft <path>
 *        ->  next ask hits Tier-1 forever
 *
 * Behavior:
 *   1. Validates the draft exists and has status=draft.
 *   2. Moves the file to domains/<domain>/blocks/<slug>.dql when that domain
 *      layout exists, otherwise blocks/<domain>/<slug>.dql.
 *   3. Flips `status = "draft"` to `status = "certified"`.
 *   4. Optionally sets `datalex_contract = "<contract>"` (provided via
 *      --contract or taken from `proposed_contract_id` with `@1` appended).
 *   5. Sets `owner = "<owner>"` from --owner if provided.
 *   6. Drops Tier-2 proposal metadata fields (their job is done).
 *   7. Computes the optional patch the human still needs to apply to
 *      datalex-manifest.json when a contract is present.
 *
 * Does NOT auto-modify datalex-manifest.json. That file is hand-curated
 * until the DataLex compiler ships its v1 emitter; this command shows
 * the user the exact diff to apply.
 */
export function promoteFromDraft(projectRoot, flags) {
    const draftPathInput = flags.fromDraft;
    const draftAbs = resolve(projectRoot, draftPathInput);
    if (!existsSync(draftAbs)) {
        return { ok: false, message: `Draft not found at ${draftPathInput}.` };
    }
    const content = readFileSync(draftAbs, 'utf-8');
    if (!/status\s*=\s*"draft"/.test(content)) {
        return {
            ok: false,
            message: `${draftPathInput} does not have status="draft". Use dql certify <path> (without --from-draft) to evaluate governance rules.`,
        };
    }
    const proposedContractId = pickField(content, 'proposed_contract_id') ?? '';
    const contract = flags.contract || (proposedContractId ? `${proposedContractId}@1` : '');
    const domain = flags.domain || pickField(content, 'proposed_domain') || guessDomain(contract) || pickField(content, 'domain');
    if (!domain) {
        return {
            ok: false,
            message: 'No --domain provided and could not infer one from the draft or contract id.',
        };
    }
    const slug = inferSlugFromPath(draftAbs);
    const destRel = resolvePromotedBlockPath(projectRoot, domain, slug, draftPathInput);
    const destAbs = join(projectRoot, destRel);
    if (existsSync(destAbs) && !flags.force) {
        return {
            ok: false,
            message: `${shortPath(projectRoot, destAbs)} already exists. ` +
                'Pass --force to overwrite, or rename the draft.',
        };
    }
    const promoted = renderPromoted(content, {
        contract: contract || undefined,
        domain,
        owner: flags.owner || pickField(content, 'owner') || '',
    });
    mkdirSync(dirname(destAbs), { recursive: true });
    writeFileSync(destAbs, promoted);
    // The draft and the certified copy must not coexist. Drop the draft now
    // that the certified file is on disk.
    unlinkSync(draftAbs);
    const datalexManifestDiff = contract
        ? renderDataLexManifestDiff({
            contract,
            domain,
            slug,
            description: pickField(content, 'description') ?? '',
        })
        : undefined;
    return {
        ok: true,
        message: `Promoted ${shortPath(projectRoot, draftAbs)} -> ${shortPath(projectRoot, destAbs)}.`,
        certifiedPath: shortPath(projectRoot, destAbs),
        datalexManifestDiff,
    };
}
// -- helpers ---------------------------------------------------------------
function pickField(content, key) {
    const m = content.match(new RegExp(`${key}\\s*=\\s*"([^"]*)"`)) ||
        content.match(new RegExp(`${key}\\s*=\\s*"""([\\s\\S]*?)"""`));
    return m?.[1]?.trim();
}
function guessDomain(contractId) {
    const head = contractId.split('.')[0];
    return head && head !== contractId ? head : null;
}
function inferSlugFromPath(absPath) {
    return absPath.replace(/.*\//, '').replace(/\.dql$/, '');
}
function resolvePromotedBlockPath(projectRoot, domain, slug, draftPathInput) {
    const normalizedDraft = draftPathInput.replaceAll('\\', '/').replace(/^\/+/, '');
    const domainFirstDraft = normalizedDraft.match(/^domains\/([^/]+)\/blocks\/_drafts\/[^/]+\.dql$/);
    if (domainFirstDraft) {
        return `domains/${domainFirstDraft[1]}/blocks/${slug}.dql`;
    }
    const safeDomain = domain
        .trim()
        .toLowerCase()
        .replace(/[^a-z0-9/_-]+/g, '-')
        .replace(/^\/+|\/+$/g, '');
    if (safeDomain && existsSync(join(projectRoot, 'domains', safeDomain))) {
        return `domains/${safeDomain}/blocks/${slug}.dql`;
    }
    return `blocks/${safeDomain || domain}/${slug}.dql`;
}
function shortPath(projectRoot, abs) {
    const root = projectRoot.endsWith('/') ? projectRoot : projectRoot + '/';
    return abs.startsWith(root) ? abs.slice(root.length) : abs;
}
function renderPromoted(source, fields) {
    let out = source;
    // Flip status.
    out = out.replace(/status\s*=\s*"draft"/, 'status = "certified"');
    // Set domain (overwrite the existing one).
    out = out.replace(/(domain\s*=\s*)"[^"]*"/, `$1"${fields.domain}"`);
    // Set datalex_contract when DataLex interop is used. Otherwise remove empty
    // placeholders so OSS-only certified blocks do not carry unresolved contracts.
    if (fields.contract) {
        if (/datalex_contract\s*=\s*"[^"]*"/.test(out)) {
            out = out.replace(/datalex_contract\s*=\s*"[^"]*"/, `datalex_contract = "${fields.contract}"`);
        }
        else {
            out = out.replace(/(status\s*=\s*"certified"\s*\n)/, `$1    datalex_contract = "${fields.contract}"\n`);
        }
    }
    else {
        out = out.replace(/\n\s*datalex_contract\s*=\s*""\s*/g, '\n');
    }
    // Set owner if provided + the field exists in the file.
    if (fields.owner) {
        if (/owner\s*=\s*"[^"]*"/.test(out)) {
            out = out.replace(/owner\s*=\s*"[^"]*"/, `owner = "${fields.owner}"`);
        }
        else {
            // Insert owner right after the status line.
            out = out.replace(/(status\s*=\s*"certified"\s*\n)/, `$1    owner = "${fields.owner}"\n`);
        }
    }
    // Drop Tier-2 provenance metadata. Its job is done; the certified block is
    // the new source of truth and should not carry capture metadata.
    out = out.replace(/\n\s*_proposed\s*\{[^}]*\}\s*\n/, '\n');
    out = out.replace(/\n\s*(asked_times|first_asked|last_asked|proposed_contract_id|proposed_domain|proposed_entity|upstream_refs)\s*=\s*(?:"[^"]*"|"""[\s\S]*?"""|\d+|\[[^\]]*\])\s*/g, '\n');
    return out;
}
function renderDataLexManifestDiff(fields) {
    const [contractId, versionStr] = fields.contract.split('@');
    const version = Number.parseInt(versionStr ?? '1', 10) || 1;
    const parts = contractId.split('.');
    const datalexDomain = parts[0] ?? fields.domain;
    const entity = parts[1] ?? 'Unknown';
    const contractName = parts[2] ?? fields.slug;
    const safeDesc = fields.description.replace(/"/g, '\\"').replace(/\n/g, ' ');
    return `Add the following entry under domains[name=${datalexDomain}].entities[name=${entity}].contracts in datalex-manifest.json:

  {
    "id": "${contractId}",
    "name": "${contractName}",
    "version": ${version},
    "description": "${safeDesc}",
    "signature": {
      "inputs": [],
      "outputs": []
    },
    "owner": "<owner>",
    "tags": []
  }

Then re-run \`dql compile\` to refresh dql-manifest.json. The DataLex compiler
will emit this entry automatically once the v1 manifest emitter ships;
until then, hand-edit \`datalex-manifest.json\` (or its source YAML) so the
DQL block's datalex_contract reference resolves.
`;
}
//# sourceMappingURL=promote-from-draft.js.map