/**
 * File writer — handles writing a single template file with
 * overwrite/lock logic, binary copy, and .origin file creation.
 *
 * Preservation regions: when a file has AURORA:<NAME>-START/END markers,
 * per-region hashes in the lockfile decide (on regeneration) whether to
 * propagate template improvements or to preserve the user's custom body.
 */
import chalk from 'chalk';
import fs from 'node:fs';
import path from 'node:path';
import { ALLOWED_RENDER_EXTENSIONS, FILE_TAGS, } from '../domain/config.js';
import { replaceFilename } from './filename-resolver.js';
import { findLockFile, hashRegionBodies, mergePreservationRegions, sha1, sha1WithPreservation, } from './lock-file.js';
import { formatCode } from './prettier-engine.js';
import { render } from './template-engine.js';
export async function writeFile(logger, originFilePath, file, options, ctx) {
    const nameReplaced = replaceFilename(file, options);
    const relativeFilePath = path.join(ctx.targetRelativePath, nameReplaced);
    const writePath = path.join(ctx.targetBasePath, relativeFilePath);
    const existFile = fs.existsSync(writePath);
    const fileExtension = path.extname(originFilePath.replace(/\.eta$/, ''));
    // Binary file → straight copy
    if (!ALLOWED_RENDER_EXTENSIONS.includes(fileExtension)) {
        if (existFile && !options.force) {
            logger.log('%s %s exist', chalk.yellow.bold('[INFO]'), nameReplaced);
        }
        else {
            fs.copyFileSync(originFilePath, writePath, fs.constants.COPYFILE_FICLONE);
            logger.log('%s %s', chalk.green.bold('[FILE COPIED]'), nameReplaced);
        }
        return;
    }
    let contents = fs.readFileSync(originFilePath, 'utf8');
    if (options.useTemplateEngine !== false) {
        contents = render(originFilePath, {
            ...options.templateData,
            currentAdditionalApi: options.currentAdditionalApi,
            currentProperty: options.currentProperty,
            writePath,
        });
    }
    // IMPORTANT: format with the consumer project's Prettier BEFORE writing & hashing.
    // Region bodies are never reformatted — preserved bodies are copied byte-for-byte
    // after Prettier has run on the full newContent.
    contents = await formatCode(contents, writePath);
    if (existFile && !options.force) {
        logger.log('%s %s exist', chalk.yellow.bold('[INFO]'), nameReplaced);
    }
    else if (existFile) {
        handleExistingFile(logger, {
            contents,
            lockFiles: options.lockFiles ?? [],
            nameReplaced,
            relativeFilePath,
            result: ctx.result,
            targetRelativePath: ctx.targetRelativePath,
            verbose: options.verbose,
            writePath,
        });
    }
    else {
        // New file — no merge; template body is canonical
        const dir = path.dirname(writePath);
        if (!fs.existsSync(dir))
            fs.mkdirSync(dir, { recursive: true });
        fs.writeFileSync(writePath, contents, 'utf8');
        logger.log('%s %s', chalk.green.bold('[FILE CREATED]'), nameReplaced);
    }
    // Track in lock files — integrity and region hashes reflect the TEMPLATE
    // output (`contents`), NOT the merged on-disk content. This is critical:
    // priorHashes must represent "what the template last produced" so that
    // the next regeneration can detect user edits (disk ≠ last template body).
    // Hashing the merged disk content would corrupt priorHashes when a user
    // body is preserved — on the next template change the preserved body
    // would look unchanged and be silently overwritten.
    const posixPath = path.sep === '\\' ? relativeFilePath.replaceAll('\\', '/') : relativeFilePath;
    const regions = hashRegionBodies(contents);
    const lockEntry = {
        integrity: `sha1:${sha1WithPreservation(contents)}`,
        path: posixPath,
    };
    if (Object.keys(regions).length > 0) {
        lockEntry.regions = regions;
    }
    ctx.result.lockFiles.push(lockEntry);
}
function logRegionOutcomes(logger, params, merge) {
    if (params.verbose) {
        for (const name of merge.updated) {
            logger.log('%s %s: %s', chalk.magenta.bold('[REGION UPDATED]'), params.nameReplaced, name);
        }
        for (const name of merge.preserved) {
            logger.log('%s %s: %s', chalk.cyan.bold('[REGION PRESERVED]'), params.nameReplaced, name);
        }
    }
    // Dropped is always shown, regardless of --verbose
    for (const name of merge.dropped) {
        logger.log('%s %s: %s', chalk.redBright.bold('[REGION DROPPED]'), params.nameReplaced, name);
    }
}
function handleExistingFile(logger, params) {
    const currentFile = fs.readFileSync(params.writePath, 'utf8');
    // FILE_TAGS sentinels are static full-file hashes — not region-aware.
    // Ignored files skip the entire pipeline (no merge, no .origin).
    const currentFileHash = sha1(currentFile);
    const firstLine = currentFile.split(/\r?\n/)[0] ?? '';
    const firstLineHash = sha1(firstLine);
    const ignoredHashes = [
        FILE_TAGS.ignoredFile,
        FILE_TAGS.ignoredGraphQLFile,
        FILE_TAGS.ignoredHtmlFile,
    ];
    if (ignoredHashes.includes(currentFileHash) ||
        ignoredHashes.includes(firstLineHash)) {
        logger.log('%s %s', chalk.cyanBright.bold('[IGNORED FILE]'), params.nameReplaced);
        return;
    }
    // Region-aware merge: decide per-region body (template vs disk) using
    // priorHashes from the lockfile. If priorHashes is absent (legacy lockfile
    // or no entry), mergePreservationRegions falls back to "preserve all".
    const lockEntry = findLockFile(params.lockFiles, params.relativeFilePath);
    const merge = mergePreservationRegions(params.contents, currentFile, lockEntry?.regions);
    const currentSkeletonHash = sha1WithPreservation(currentFile);
    const skeletonUnchanged = !lockEntry || lockEntry.integrity === `sha1:${currentSkeletonHash}`;
    if (skeletonUnchanged) {
        fs.writeFileSync(params.writePath, merge.content, 'utf8');
        if (params.verbose) {
            logger.log('%s %s', chalk.magenta.bold('[FILE OVERWRITE]'), params.nameReplaced);
        }
        logRegionOutcomes(logger, params, merge);
        return;
    }
    // Skeleton was modified by user → create .origin with merged regions so
    // the manual merge already carries the user's custom bodies.
    const originPath = params.writePath.replace(/\.(?=[^.]*$)/, '.origin.');
    fs.writeFileSync(originPath, merge.content, 'utf8');
    const originFileName = params.nameReplaced.replace(/\.(?=[^.]*$)/, '.origin.');
    params.result.originFiles.push(path.join(params.targetRelativePath, originFileName));
    logger.log('%s %s', chalk.redBright.bold('[ORIGIN FILE CREATED]'), originFileName);
    logRegionOutcomes(logger, params, merge);
}
