/// usage: deno run -A resources/generate-ast-for-unicode.ts | rustfmt > tmp.rs

import * as path from "jsr:@std/path";

const UNICODE_DATA_TXT: string =
    "https://www.unicode.org/Public/UCD/latest/ucd/UnicodeData.txt";
const BLOCKS_TXT: string =
    "https://www.unicode.org/Public/UCD/latest/ucd/Blocks.txt";

const resp = await fetch(UNICODE_DATA_TXT);
const data = await resp.text();

const properties: { [key: string]: string[] } = {};
for (const line of data.split("\n").map((line) => line.trim())) {
    const cols = line.split(";");
    if (cols.length < 3) {
        continue;
    }
    if (cols[2].toLowerCase() === "cs") {
        // surrogate code point
        continue;
    }
    if (!properties[cols[2]]) {
        properties[cols[2]] = [];
    }
    properties[cols[2]].push(cols[0]);
}

console.log(`//! Generated by ${path.basename(import.meta.url)}
use crate::ast::ASTNode;
`);
const categories: { [key: string]: string[] } = {};
for (const [key, value] of Object.entries(properties)) {
    const k = key.toLowerCase();
    if (!categories[k[0]]) {
        categories[k[0]] = [];
    }
    categories[k[0]].push(k);
    const table_name: string = `GENERAL_CATEGORY_${key.toUpperCase()}`;
    console.log(`pub const ${table_name}: &[(char, char)] = &[`);
    let start = -1;
    let end = -1;
    for (const v of value) {
        const value = Number.parseInt(v, 16);
        if (start < 0) {
            start = value;
            end = value;
        } else if (value - end == 1) {
            end = value;
        } else {
            console.log(
                `('\\u{${start.toString(16)}}', '\\u{${end.toString(16)}}'),`,
            );

            start = value;
            end = value;
        }
    }
    if (start > 0) {
        console.log(
            `('\\u{${start.toString(16)}}', '\\u{${end.toString(16)}}'),`,
        );
    }

    console.log("];");

    console.log(`
    pub fn alternate_general_category_${k}() -> ASTNode<char> {
        ASTNode::alternate_all(${table_name}.iter().copied()).unwrap()
    }`);

    console.log(`
    pub fn negate_general_category_${k}() -> ASTNode<char> {
        ASTNode::negate_all(${table_name}.iter().copied()).unwrap()
    }\n`);
}

for (const [key, value] of Object.entries(categories)) {
    console.log(
        `pub fn iterate_general_category_${key.toLowerCase()}() -> impl Iterator<Item = (char,char)> {`,
    );
    const v = value.pop();
    console.log(`GENERAL_CATEGORY_${v?.toUpperCase()}.iter()`);
    while (value.length != 0) {
        const v = value.pop();
        console.log(`.chain(GENERAL_CATEGORY_${v?.toUpperCase()}.iter())`);
    }
    console.log(".copied()}\n");
}

const block_txt = await (await fetch(BLOCKS_TXT)).text();
const blocks: [string, string[]][] = [];
// https://www.unicode.org/versions/Unicode16.0.0/UnicodeStandard-16.0.pdf
// Chapter 3.4 Characters and Encoding D10b
for (const line of block_txt.split("\n").map((line) => line.trim())) {
    if (line.startsWith("#")) {
        continue;
    }
    const cols = line.split(";");
    if (cols.length < 2) {
        continue;
    }

    // https://www.unicode.org/reports/tr44/#UAX44-LM3
    // > ignore case, whitespace, underscore, hyphens and any initial prefix string "is"
    // The prefix “is” is not included in the block name, so there is no need to remove the prefix.
    // Case is ignored, so convert everything to lowercase
    // and remove whitespaces, hyphens, and underscores.
    const block_name = cols[1].trim().replaceAll(/[\s\-_]/g, "").toLowerCase();
    if (
        block_name === "lowsurrogates" || block_name === "highsurrogates" ||
        block_name === "highprivateusesurrogates"
    ) {
        continue;
    }
    const range = cols[0].split("..");
    blocks.push([block_name, range]);
}

blocks.sort();
console.log("pub const BLOCK_TO_RANGES: &[(&str, char, char)] = &[");
for (const [block_name, range] of blocks) {
    console.log(`("${block_name}", '\\u{${range[0]}}', '\\u{${range[1]}}'),`);
}
console.log("];\n");

console.log(`
pub fn seach_block_range(block_name: &str) -> Option<(char, char)> {
    // https://www.unicode.org/reports/tr44/#UAX44-LM3
    // > ignore case, whitespace, underscore, hyphens and any initial prefix string "is"
    // The prefix “is” is not included in the block name, so there is no need to remove the prefix.
    // Case is ignored, so convert everything to lowercase
    // and remove whitespaces, hyphens, and underscores.
    let block_name = block_name.trim().replace(|c: char| c.is_whitespace() || c == '-' || c == '_', "").to_lowercase();
    BLOCK_TO_RANGES
        .binary_search_by_key(&block_name.as_str(), |k| k.0)
        .ok()
        .map(|i| (BLOCK_TO_RANGES[i].1, BLOCK_TO_RANGES[i].2))
}
`);
