import fs from 'fs';
import path from 'path';
import { glob } from 'glob';
import { IdentitySet, formatPosition, Position } from '@preserves/core';
import { readSchema } from '../reader';
import chalk from 'chalk';
import * as M from '../meta';

export interface Diagnostic {
    type: 'warn' | 'error';
    file: string | null;
    detail: Error | { message: string, pos: Position | null };
};

export type InputFile = {
    inputBaseDir: string,
    inputFilePath: string,
    text: string,
    baseRelPath: string,
    modulePath: M.ModulePath,
    schema: M.Schema,
};

export type XrefFile = {
    xrefFilePath: string,
    text: string,
    modulePath: M.ModulePath,
    schema: M.Schema,
};

export type Expanded = {
    baseDirs: Array<string>,
    inputFiles: Array<InputFile>,
    xrefFiles: Array<XrefFile>,
    failures: Array<Diagnostic>,
};

export function computeBase(paths: string[]): string {
    if (paths.length === 0) {
        return '';
    } else if (paths.length === 1) {
        const d = path.dirname(paths[0]);
        return (d === '.') ? '' : d + '/';
    } else {
        let i = 0;
        while (true) {
            let ch: string | null = null
            for (const p of paths) {
                if (i >= p.length) return p.slice(0, i);
                if (ch === null) ch = p[i];
                if (p[i] !== ch) return p.slice(0, i);
            }
            i++;
        }
    }
}

export function inputToInputGlob(userInput: string): { baseDir: string, glob: string } {
    const colonPos = userInput.lastIndexOf(':');
    let [base, g] = (colonPos === -1)
        ? [userInput, '**/*.prs']
        : [userInput.slice(0, colonPos), userInput.slice(colonPos + 1)];
    if (base[base.length - 1] !== '/') {
        base = base + '/';
    }
    return { baseDir: base, glob: g };
}

export function findSchemas(userInput: string): ({
    names: string[],
    base: string,
}) {
    const { baseDir: base, glob: g } = inputToInputGlob(userInput);
    const names = glob.sync(base + g);
    return { names, base };
}

export function expandInputGlob(input: string[], xrefs: string[]): Expanded {
    const failures: Array<Diagnostic> = [];
    const baseDirs = new IdentitySet<string>();

    function processInputFile(base: string, filePath: string, xref: true): XrefFile[];
    function processInputFile(base: string, filePath: string, xref: false): InputFile[];
    function processInputFile(base: string, filePath: string, xref: boolean): (InputFile | XrefFile)[] {
        if (!filePath.startsWith(base)) {
            throw new Error(`Input filename ${filePath} falls outside base ${base}`);
        }
        try {
            const text = fs.readFileSync(filePath, 'utf-8');
            const baseRelPath = filePath.slice(base.length);
            const modulePath = baseRelPath.split('/').map(p => p.split('.')[0]).map(Symbol.for);
            const schema = readSchema(text, {
                name: filePath,
                readInclude(includePath: string): string {
                    return fs.readFileSync(
                        path.resolve(path.dirname(filePath), includePath),
                        'utf-8');
                },
            });
            if (xref) {
                return [{ xrefFilePath: filePath, text, modulePath, schema }];
            } else {
                return [{ inputBaseDir: base, inputFilePath: filePath, text, baseRelPath, modulePath, schema }];
            }
        } catch (e) {
            failures.push({ type: 'error', file: filePath, detail: e as Error });
            return [];
        }
    }

    const inputFiles = input.flatMap(userInput => {
        const { names, base } = findSchemas(userInput);
        baseDirs.add(base);
        return names.flatMap(f => processInputFile(base, f, false));
    });

    const xrefFiles = xrefs.flatMap(userInput => {
        const { names, base } = findSchemas(userInput);
        baseDirs.add(base);
        return names.flatMap(f => processInputFile(base, f, true));
    });

    return {
        baseDirs: Array.from(baseDirs),
        inputFiles,
        xrefFiles,
        failures,
    };
}

export function changeExt(p: string, newext: string): string {
    return p.slice(0, -path.extname(p).length) + newext;
}

export function formatFailures(failures: Array<Diagnostic>, traceback = false): void {
    for (const d of failures) {
        console.error(
            (d.type === 'error' ? chalk.redBright('[ERROR]') : chalk.yellowBright('[WARNING]'))
                + ' '
                + chalk.blueBright(formatPosition((d.detail as any).pos ?? d.file))
                + ': '
                + d.detail.message
                + (traceback && (d.detail instanceof Error)
                    ? '\n' + d.detail.stack
                    : ''));
    }
    if (failures.length > 0) {
        console.error();
    }
}
