import * as fs from 'fs';
import * as path from 'path';
import { findTemplate, ProjectTemplate, TEMPLATES, ProjectTemplateId } from './templates';
import { isInside } from '../lib/paths';
import { logError, logInfo } from '../utils';

/**
 * Project scaffolder.
 *
 * Mirrors Connect_origin's Developer-agent quick-start (3 templates: static /
 * vite-vanilla / vite-react), refactored as:
 *   - templates as data (see templates.ts) so adding new ones is a one-file change,
 *   - service interface + filesystem implementation for testability,
 *   - validation in the service (not the command handler) so callers can't bypass.
 */

export interface ScaffoldRequest {
    /** User-supplied project name. Will be re-validated; service rejects bad input. */
    name: string;
    template: ProjectTemplateId;
    /** Absolute parent directory. The project will land at `<rootDir>/<name>/`. */
    rootDir: string;
}

export type ScaffoldResult =
    | { ok: true; projectPath: string; filesWritten: string[] }
    | { ok: false; error: string; code: ScaffoldErrorCode };

export type ScaffoldErrorCode =
    | 'INVALID_NAME'
    | 'NO_ROOT_DIR'
    | 'ROOT_NOT_ABSOLUTE'
    | 'ALREADY_EXISTS'
    | 'UNKNOWN_TEMPLATE'
    | 'WRITE_FAILED';

export interface IProjectScaffolder {
    scaffold(req: ScaffoldRequest): Promise<ScaffoldResult>;
    listTemplates(): ProjectTemplate[];
}

const NAME_RE = /^[a-zA-Z0-9_-]{2,40}$/;

/** Conservative project-name validator. Same constraints as Connect_origin's command UI. */
export function validateProjectName(raw: string): string | null {
    if (typeof raw !== 'string') return null;
    const trimmed = raw.trim();
    return NAME_RE.test(trimmed) ? trimmed : null;
}

export interface ScaffolderDeps {
    /** Optional fs override for tests. Defaults to node:fs. */
    fsImpl?: Pick<typeof fs, 'existsSync' | 'mkdirSync' | 'writeFileSync'>;
}

export class FileSystemProjectScaffolder implements IProjectScaffolder {
    private readonly _fs: NonNullable<ScaffolderDeps['fsImpl']>;

    constructor(deps: ScaffolderDeps = {}) {
        this._fs = deps.fsImpl ?? fs;
    }

    listTemplates(): ProjectTemplate[] {
        return TEMPLATES.slice();
    }

    async scaffold(req: ScaffoldRequest): Promise<ScaffoldResult> {
        const name = validateProjectName(req.name);
        if (!name) {
            return { ok: false, error: '프로젝트 이름은 영문/숫자/_/- 만 허용되며 2~40자여야 합니다.', code: 'INVALID_NAME' };
        }
        if (!req.rootDir) {
            return { ok: false, error: '대상 폴더가 지정되지 않았습니다. 워크스페이스를 먼저 여세요.', code: 'NO_ROOT_DIR' };
        }
        if (!path.isAbsolute(req.rootDir)) {
            return { ok: false, error: '대상 폴더는 절대 경로여야 합니다.', code: 'ROOT_NOT_ABSOLUTE' };
        }
        const template = findTemplate(req.template);
        if (!template) {
            return { ok: false, error: `알 수 없는 템플릿: ${req.template}`, code: 'UNKNOWN_TEMPLATE' };
        }

        const projectPath = path.join(req.rootDir, name);
        if (this._fs.existsSync(projectPath)) {
            return { ok: false, error: `이미 존재하는 폴더입니다: ${projectPath}`, code: 'ALREADY_EXISTS' };
        }

        const fileMap = template.files(name);
        const filesWritten: string[] = [];
        try {
            this._fs.mkdirSync(projectPath, { recursive: true });
            for (const [rel, contents] of Object.entries(fileMap)) {
                const target = path.join(projectPath, rel);
                if (!isInside(projectPath, target)) {
                    // Defense in depth — a template author can't smuggle "../" into a path.
                    throw new Error(`Template path escapes project root: ${rel}`);
                }
                this._fs.mkdirSync(path.dirname(target), { recursive: true });
                this._fs.writeFileSync(target, contents, 'utf8');
                filesWritten.push(target);
            }
        } catch (e: any) {
            const msg = e?.message ?? String(e);
            logError('Project scaffold failed.', { name, template: template.id, error: msg });
            return { ok: false, error: `생성 실패: ${msg}`, code: 'WRITE_FAILED' };
        }

        logInfo('Project scaffolded.', { name, template: template.id, projectPath, fileCount: filesWritten.length });
        return { ok: true, projectPath, filesWritten };
    }
}
