import path from 'node:path'
import config from '@aklive2d/config'
import { file } from '@aklive2d/libs'
import type { OfficialInfoOperatorConfigV2 } from '@aklive2d/official-info/types'
import unidecode from 'unidecode'
import { DIST_DIR, OPERATOR_SOURCE_FOLDER } from '../index.ts'
import {
    CharacterTableJson,
    Codename,
    OperatorEntryType,
    SkinTableJson,
    SkinTableJsonCharSkinEntry,
} from '../types.ts'

export const getExtractedFolder = (name: string) => {
    return path.join(OPERATOR_SOURCE_FOLDER, name, config.dir_name.extracted)
}

export const getDistFolder = (name: string) => {
    return path.join(DIST_DIR, config.module.operator.operator, name)
}

export const getVoiceFolders = (name: string) => {
    return config.dir_name.voice.sub.map((sub) =>
        path.join(
            OPERATOR_SOURCE_FOLDER,
            name,
            config.dir_name.voice.main,
            sub.name
        )
    )
}

export const findLogo = (characterTable: CharacterTableJson, id: string) => {
    const SEARCH_ORDER = ['teamId', 'groupId', 'nationId'] as const
    let logo: string | null = null
    const entry = characterTable[id]
    if (!entry) throw new Error(`Character ${id} not found`)
    for (const key of SEARCH_ORDER) {
        logo = entry[key]
        if (logo) break
    }
    if (logo === null) throw new Error(`Logo not found for character ${id}`)
    let invert_filter = true
    if (logo === 'rhodes') {
        logo = 'rhodes_override'
        invert_filter = false
    }
    logo = `logo_${logo}`
    const logoFiles = file
        .readdirSync(
            path.resolve(
                OPERATOR_SOURCE_FOLDER,
                config.module.operator.logos_assets
            )
        )
        .map((e) => {
            const name = path.parse(e).name
            return {
                file: name,
                name: name.toLowerCase(),
            }
        })
    const logoFile = logoFiles.find((f) => f.name === logo)
    if (!logoFile)
        throw new Error(`Logo file not found for character ${id}, ${logo}`)
    logo = logoFile.file

    return {
        logo,
        invert_filter,
    }
}

export const findSkinEntry = (
    skinTableJson: SkinTableJson,
    name: string,
    type: OperatorEntryType
) => {
    const OVERWRITE_ENTRIES = {
        WISADEL: "Wiš'adel",
    } as const
    type OverwriteKeys = keyof typeof OVERWRITE_ENTRIES

    name = OVERWRITE_ENTRIES[name as OverwriteKeys] ?? name
    const dynSkinEntries = Object.values(skinTableJson.charSkins).filter(
        (entry) => entry.dynIllustId !== null
    )
    let entry: SkinTableJsonCharSkinEntry | undefined
    if (type === 'operator') {
        entry = dynSkinEntries.find(
            (e) => e.displaySkin.modelName?.toLowerCase() === name.toLowerCase()
        )
    } else if (type === 'skin') {
        entry = dynSkinEntries.find(
            (e) => e.displaySkin.skinName?.toLowerCase() === name.toLowerCase()
        )
    } else {
        throw new Error(`Invalid type: ${type}`)
    }
    if (!entry) throw new Error(`Skin entry not found for ${name}`)
    return entry
}

/**
 * Name from Official Info sometimes is incorrect, can only be used as a
 * reference
 * @param skinEntry
 * @param officialInfo
 * @returns
 */
export const findCodename = (
    skinEntry: SkinTableJsonCharSkinEntry,
    officialInfo: OfficialInfoOperatorConfigV2
) => {
    const UPPER_CASE_EXCEPTION_WORDS = [
        'the',
        'is',
        'of',
        'and',
        'for',
        'a',
        'an',
        'are',
        'in',
        'as',
    ]
    const codename: Codename = { 'zh-CN': '', 'en-US': '' }
    const regexp = /[^(\w) ]/
    let modelNameNormalized = skinEntry.displaySkin.modelName
    const hasSpecialCharInModelName = regexp.test(
        skinEntry.displaySkin.modelName
    )
    if (hasSpecialCharInModelName) {
        modelNameNormalized = unidecode(modelNameNormalized).replace(regexp, '')
        const modelNameArray = skinEntry.displaySkin.modelName.split(' ')
        const modelNameNormalizedArray = modelNameNormalized.split(' ')
        modelNameArray.forEach((word, index) => {
            if (word !== modelNameNormalizedArray[index]) {
                modelNameNormalizedArray[index] =
                    `${word}/${modelNameNormalizedArray[index]}`
            }
        })
        modelNameNormalized = modelNameNormalizedArray.join(' ')
    }
    if (skinEntry.displaySkin.skinName) {
        let engSkinName = officialInfo.skinName['en-US']
        const engkinNameArray = engSkinName.split(' ')
        engkinNameArray.forEach((word, index) => {
            if (/^[a-zA-Z]+$/.test(word)) {
                word = word.toLowerCase()
                if (UPPER_CASE_EXCEPTION_WORDS.includes(word) && index !== 0) {
                    engkinNameArray[index] = word
                } else {
                    engkinNameArray[index] =
                        word[0].toUpperCase() + word.slice(1)
                }
            }
        })
        engSkinName = engkinNameArray.join(' ')
        codename['zh-CN'] =
            `${officialInfo.skinName['zh-CN']} · ${officialInfo.operatorName}`
        codename['en-US'] = `${engSkinName} / ${modelNameNormalized}`
    } else {
        codename['zh-CN'] = officialInfo.operatorName
        codename['en-US'] = modelNameNormalized
    }
    return codename
}

export const getActualFilename = (
    filename: string,
    dir: string,
    isSP: boolean = false
) => {
    if (isSP)
        filename = `${config.module.operator.sp_filename_prefix}${filename}`
    const files = file.readdirSync(dir)
    const actualFilename = files.find((e) => {
        const name = path.parse(e).name
        return filename.startsWith(name) && !name.endsWith('_Start')
    })
    return actualFilename ? path.parse(actualFilename).name : filename
}

export const findSkel = (name: string, dir: string) => {
    const files = file.readdirSync(dir)
    const skel = files.find((e) => {
        const actualName = path.parse(e)
        return (
            name.startsWith(actualName.name) &&
            !actualName.name.endsWith('_Start') &&
            actualName.ext === '.skel'
        )
    })
    const json = files.find((e) => {
        const actualName = path.parse(e)
        return (
            name.startsWith(actualName.name) &&
            !actualName.name.endsWith('_Start') &&
            actualName.ext === '.json'
        )
    })
    if (skel) {
        return skel
    } else if (json) {
        return json
    } else {
        throw new Error('No skel or json file found')
    }
}
