/*
 * Copyright 2026, Jaime Terrats.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { SfError, Messages } from '@salesforce/core';
import { SfCommand, Flags } from '@salesforce/sf-plugins-core';
import { generatePdfRegistry } from '../../generators/pdf-registry.js';
import { FileWriter } from '../../services/file-writer.js';
import { readWorkspaceConfig } from '../../services/workspace-config.js';
import { ALL_PROFILES } from '../../profiles/index.js';
import { PLUGIN_VERSION } from '../../version.js';
import { cleanLegacyDirs, detectTools, findContentDrift, findStaleFiles, inferProfiles, } from '../../util/command-helpers.js';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@jterrats/setup-agents', 'setup-agents.update');
export default class Update extends SfCommand {
    static summary = messages.getMessage('summary');
    static description = messages.getMessage('description');
    static examples = messages.getMessages('examples');
    static flags = {
        'dry-run': Flags.boolean({
            summary: messages.getMessage('flags.dry-run.summary'),
            description: messages.getMessage('flags.dry-run.description'),
            default: false,
        }),
        check: Flags.boolean({
            summary: messages.getMessage('flags.check.summary'),
            description: messages.getMessage('flags.check.description'),
            default: false,
        }),
        yes: Flags.boolean({
            char: 'y',
            summary: messages.getMessage('flags.yes.summary'),
            description: messages.getMessage('flags.yes.description'),
            default: false,
        }),
        'doc-reference': Flags.boolean({
            summary: messages.getMessage('flags.doc-reference.summary'),
            default: false,
        }),
        'fetch-refs': Flags.boolean({
            summary: messages.getMessage('flags.fetch-refs.summary'),
            default: false,
        }),
        force: Flags.boolean({
            char: 'f',
            summary: messages.getMessage('flags.force.summary'),
            default: false,
            exclusive: ['dry-run', 'check'],
        }),
    };
    async run() {
        const { flags } = await this.parse(Update);
        const cwd = process.cwd();
        if (flags['doc-reference']) {
            const docReference = this.runDocReference(cwd);
            return {
                updated: [],
                skipped: [],
                staleFiles: [],
                availableActions: ['check', 'dry-run', 'update'],
                cwd,
                docReference,
            };
        }
        if (flags['fetch-refs']) {
            const fetchRefs = await this.runFetchRefs(cwd);
            return {
                updated: [],
                skipped: [],
                staleFiles: [],
                availableActions: ['check', 'dry-run', 'update'],
                cwd,
                fetchRefs,
            };
        }
        if (flags.force) {
            cleanLegacyDirs(cwd);
            const forceResult = await this.runForce(cwd);
            return {
                updated: forceResult.updated,
                skipped: [],
                staleFiles: [],
                availableActions: ['check', 'dry-run', 'update'],
                cwd,
            };
        }
        cleanLegacyDirs(cwd);
        const staleFiles = findStaleFiles(cwd);
        if (staleFiles.length === 0) {
            this.log(messages.getMessage('info.nothingToUpdate', [PLUGIN_VERSION]));
            return { updated: [], skipped: [], staleFiles: [], availableActions: ['check', 'dry-run'], cwd };
        }
        this.logStaleFiles(staleFiles);
        if (flags['dry-run'] || flags.check) {
            return this.runDryRunOrCheck(cwd, staleFiles, flags.check);
        }
        const cancelled = await this.promptConfirmation(flags.yes);
        if (cancelled) {
            return {
                updated: [],
                skipped: staleFiles.map((f) => f.file),
                staleFiles,
                availableActions: ['check', 'dry-run', 'update'],
                cwd,
            };
        }
        const updated = await this.applyUpdates(cwd, staleFiles);
        this.log(messages.getMessage('info.done', [updated.length.toString()]));
        return { updated, skipped: [], staleFiles, availableActions: ['check', 'dry-run', 'update'], cwd };
    }
    logStaleFiles(staleFiles) {
        this.log(messages.getMessage('info.staleFilesFound', [staleFiles.length.toString()]));
        for (const f of staleFiles) {
            const mode = f.managedBlocks ? messages.getMessage('info.managedBlocks') : messages.getMessage('info.fullFile');
            this.log(`  ${f.tool.padEnd(12)} ${f.file} (${mode})`);
        }
    }
    runDryRunOrCheck(cwd, staleFiles, isCheck) {
        const contentDrift = findContentDrift(cwd);
        const driftOnly = contentDrift.filter((d) => d.reason === 'content' && !staleFiles.some((s) => s.file === d.file));
        if (driftOnly.length > 0) {
            this.log(`Content drift detected in ${driftOnly.length} file(s):`);
            for (const d of driftOnly) {
                this.log(`  ${d.tool.padEnd(12)} ${d.file} (content changed)`);
            }
        }
        this.log(messages.getMessage(isCheck ? 'info.check' : 'info.dryRun'));
        const allAffected = [...staleFiles.map((f) => f.file), ...driftOnly.map((d) => d.file)];
        if (isCheck && allAffected.length > 0) {
            process.exitCode = 1;
        }
        return { updated: allAffected, skipped: [], staleFiles, availableActions: ['check', 'dry-run', 'update'], cwd };
    }
    async promptConfirmation(yesFlag) {
        if (yesFlag || !process.stdin.isTTY)
            return false;
        const { confirm } = await import('@inquirer/prompts');
        const proceed = await confirm({ message: messages.getMessage('prompt.confirmUpdate'), default: true });
        if (!proceed) {
            this.log(messages.getMessage('info.cancelled'));
            return true;
        }
        return false;
    }
    async applyUpdates(cwd, staleFiles) {
        const updated = [];
        const toolsToUpdate = [...new Set(staleFiles.map((f) => f.tool))];
        const inferredProfiles = inferProfiles(cwd);
        const profileArg = inferredProfiles.length > 0 ? inferredProfiles.join(',') : 'developer';
        const args = ['--force', '--profile', profileArg];
        await Promise.all(toolsToUpdate.map(async (tool) => {
            this.log(messages.getMessage('info.updating', [tool]));
            await this.config.runCommand('setup-agents:local', [...args, '--rules', tool]);
            updated.push(...staleFiles.filter((f) => f.tool === tool).map((f) => f.file));
        }));
        return updated;
    }
    async runForce(cwd) {
        const config = readWorkspaceConfig(cwd);
        if (!config) {
            throw new SfError(messages.getMessage('warn.forceNoConfig'));
        }
        const profileArg = config.profiles.length > 0 ? config.profiles.join(',') : 'developer';
        const updated = [];
        const detected = detectTools(cwd);
        const tools = [...new Set([...config.tools, ...detected])];
        await Promise.all(tools.map(async (tool) => {
            this.log(messages.getMessage('info.forceUpdating', [tool]));
            await this.config.runCommand('setup-agents:local', ['--force', '--profile', profileArg, '--rules', tool]);
            updated.push(tool);
        }));
        this.log(messages.getMessage('info.forceDone', [updated.length.toString()]));
        return { updated };
    }
    async runFetchRefs(cwd) {
        const refsDir = join(cwd, '.setup-agents', 'references');
        mkdirSync(refsDir, { recursive: true });
        const refs = [
            // Type 3 — Direct PDFs (full content)
            {
                filename: 'salesforce_apex_language_reference.pdf',
                url: 'https://resources.docs.salesforce.com/latest/latest/en-us/sfdc/pdf/salesforce_apex_language_reference.pdf',
            },
            {
                filename: 'api_meta.pdf',
                url: 'https://resources.docs.salesforce.com/latest/latest/en-us/sfdc/pdf/api_meta.pdf',
            },
            {
                filename: 'salesforce_soql_sosl.pdf',
                url: 'https://resources.docs.salesforce.com/latest/latest/en-us/sfdc/pdf/salesforce_soql_sosl.pdf',
            },
            {
                filename: 'data_cloud.pdf',
                url: 'https://resources.docs.salesforce.com/latest/latest/en-us/sfdc/pdf/data_cloud.pdf',
            },
            // Type 2 — New-style LWR HTML (partial SSR: intro + TOC only)
            {
                filename: 'b2b-b2c-comm-dev-guide.html',
                url: 'https://developer.salesforce.com/docs/commerce/salesforce-commerce/guide/b2b-b2c-comm-dev-guide.html',
            },
            {
                filename: 'agentforce-developer-guide.html',
                url: 'https://developer.salesforce.com/docs/einstein/genai/guide/agentforce-developer-guide.html',
            },
        ];
        const outcomes = await Promise.all(refs.map(async (ref) => {
            const dest = join(refsDir, ref.filename);
            if (existsSync(dest)) {
                return { kind: 'cached', filename: ref.filename };
            }
            try {
                const res = await fetch(ref.url, { signal: AbortSignal.timeout(15_000) });
                if (!res.ok) {
                    this.warn(messages.getMessage('warn.fetchRefsFailed', [ref.filename, res.status.toString()]));
                    return { kind: 'failed', filename: ref.filename };
                }
                const buffer = await res.arrayBuffer();
                writeFileSync(dest, Buffer.from(buffer));
                return { kind: 'downloaded', filename: ref.filename };
            }
            catch {
                this.warn(messages.getMessage('warn.fetchRefsFailed', [ref.filename, 'network error']));
                return { kind: 'failed', filename: ref.filename };
            }
        }));
        const downloaded = outcomes.filter((o) => o.kind === 'downloaded').map((o) => o.filename);
        const cached = outcomes.filter((o) => o.kind === 'cached').map((o) => o.filename);
        const failed = outcomes.filter((o) => o.kind === 'failed').map((o) => o.filename);
        for (const f of downloaded)
            this.log(messages.getMessage('info.fetchRefsDownloaded', [f]));
        for (const f of cached)
            this.log(messages.getMessage('info.fetchRefsCached', [f]));
        return { downloaded, cached, failed, refsDir, cwd };
    }
    runDocReference(cwd) {
        const config = readWorkspaceConfig(cwd);
        if (!config) {
            throw new SfError(messages.getMessage('warn.docReferenceNoConfig'));
        }
        const activeProfiles = ALL_PROFILES.filter((p) => config.profiles.includes(p.id));
        const writtenFiles = [];
        const writer = new FileWriter({
            force: true,
            log: () => undefined,
            warn: () => undefined,
        });
        for (const tool of config.tools) {
            if (tool !== 'cursor')
                continue;
            const rulesDir = join(cwd, '.cursor', 'rules');
            if (existsSync(rulesDir)) {
                const filePath = join(rulesDir, 'salesforce-pdf-registry.mdc');
                writer.write(filePath, generatePdfRegistry(activeProfiles, PLUGIN_VERSION));
                writtenFiles.push(filePath);
                this.log(messages.getMessage('info.docReferenceWritten', [filePath]));
            }
        }
        return { files: writtenFiles, profiles: config.profiles, cwd };
    }
}
//# sourceMappingURL=update.js.map