#!/usr/bin/env node

const fs = require('fs')
const path = require('path')
const chroma = require('chroma-js')

const usage = `
Generate and apply a condition palette from a base hex.

Usage:
  npm run palette:condition -- --name mauve --hex "#ca9ee6"

Options:
  --name        Palette key (letters, numbers, dashes)
  --hex         Base hex color (example: #74c7ec)
  --print-only  Print snippets without editing files
  --help        Show this message
`.trim()

const args = process.argv.slice(2)

if (args.includes('--help')) {
    console.log(usage)
    process.exit(0)
}

const readArg = (key) => {
    const index = args.indexOf(key)
    if (index === -1 || index + 1 >= args.length) {
        return undefined
    }

    return args[index + 1]
}

const rawName = readArg('--name')
const rawHex = readArg('--hex')
const printOnly = args.includes('--print-only')

if (!rawName || !rawHex) {
    console.error('Missing required options.\n')
    console.error(usage)
    process.exit(1)
}

const paletteKey = rawName.trim().toLowerCase().replace(/[\s_]+/g, '-')

if (!/^[a-z][a-z0-9-]*$/.test(paletteKey)) {
    console.error(`Invalid --name "${rawName}". Use letters/numbers/dashes and start with a letter.`)
    process.exit(1)
}

if (!chroma.valid(rawHex)) {
    console.error(`Invalid --hex "${rawHex}".`)
    process.exit(1)
}

const toHex = (value) => chroma(value).hex().toLowerCase()
const mixWithWhite = (base, ratio) => toHex(chroma.mix(base, 'white', ratio, 'lab'))
const mixWithBlack = (base, ratio) => toHex(chroma.mix(base, 'black', ratio, 'lab'))
const escapeRegExp = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
const toConstName = (key) => key
    .split('-')
    .map(part => part.charAt(0).toUpperCase() + part.slice(1))
    .join('')

const findMatchingBrace = (source, openBraceIndex) => {
    let depth = 0

    for (let i = openBraceIndex; i < source.length; i += 1) {
        if (source[i] === '{') {
            depth += 1
        } else if (source[i] === '}') {
            depth -= 1
            if (depth === 0) {
                return i
            }
        }
    }

    return -1
}

const insertBeforeMarker = (source, marker, insertion) => {
    const index = source.indexOf(marker)

    if (index === -1) {
        throw new Error(`Could not find marker: ${marker}`)
    }

    return source.slice(0, index) + insertion + source.slice(index)
}

const findObjectMarkerIndex = (source, markers) => {
    for (const marker of markers) {
        if (typeof marker === 'string') {
            const index = source.indexOf(marker)
            if (index !== -1) {
                return index
            }
            continue
        }

        const flags = marker.flags.replace(/g/g, '')
        const regex = new RegExp(marker.source, flags)
        const match = regex.exec(source)

        if (match && typeof match.index === 'number') {
            return match.index
        }
    }

    return -1
}

const markerToString = (marker) => (typeof marker === 'string' ? marker : marker.toString())

const upsertIntoObjectLiteral = ({source, objectStartMarker, objectStartMarkers, entryRegex, entryText}) => {
    if (entryRegex.test(source)) {
        return source
    }

    const markerCandidates = objectStartMarkers || (objectStartMarker ? [objectStartMarker] : [])
    const markerDescription = markerCandidates.map(markerToString).join(' OR ')
    const markerIndex = findObjectMarkerIndex(source, markerCandidates)

    if (markerIndex === -1) {
        throw new Error(`Could not find object marker: ${markerDescription}`)
    }

    const openBraceIndex = source.indexOf('{', markerIndex)
    if (openBraceIndex === -1) {
        throw new Error(`Could not find opening brace after marker: ${markerDescription}`)
    }

    const closeBraceIndex = findMatchingBrace(source, openBraceIndex)
    if (closeBraceIndex === -1) {
        throw new Error(`Could not find matching brace for marker: ${markerDescription}`)
    }

    let prevIndex = closeBraceIndex - 1
    while (prevIndex >= 0 && /\s/.test(source[prevIndex])) {
        prevIndex -= 1
    }

    const prevChar = source[prevIndex]
    const needsComma = prevChar && prevChar !== '{' && prevChar !== ','
    const prefix = needsComma ? ',\n' : '\n'

    return source.slice(0, closeBraceIndex) + prefix + entryText + source.slice(closeBraceIndex)
}

const base = chroma(rawHex)
const whiteMixByTone = {
    10: 0.94,
    20: 0.88,
    30: 0.80,
    40: 0.72,
    50: 0.62,
    60: 0.52,
    70: 0.42,
    80: 0.30,
    90: 0.18,
}

const tones = Object.fromEntries(
    Object.entries(whiteMixByTone).map(([tone, ratio]) => [tone, mixWithWhite(base, ratio)])
)

const shades = {
    100: mixWithBlack(base, 0.08),
    200: mixWithBlack(base, 0.16),
    300: mixWithBlack(base, 0.24),
    400: mixWithBlack(base, 0.32),
    500: mixWithBlack(base, 0.40),
}

const palette = {
    normal: toHex(base),
    lighter: tones[90],
    ...tones,
}

const pascal = toConstName(paletteKey)
const paletteObjectKey = paletteKey.includes('-') ? `'${paletteKey}'` : paletteKey

const tailwindBlock = `      ${paletteObjectKey}: {
        normal: '${palette.normal}',
        lighter: '${palette.lighter}',
        90: '${palette[90]}',
        80: '${palette[80]}',
        70: '${palette[70]}',
        60: '${palette[60]}',
        50: '${palette[50]}',
        40: '${palette[40]}',
        30: '${palette[30]}',
        20: '${palette[20]}',
        10: '${palette[10]}',
        shade: {
            100: '${shades[100]}',
            200: '${shades[200]}',
            300: '${shades[300]}',
            400: '${shades[400]}',
            500: '${shades[500]}'
        },
        tint: {
            100: '${palette[90]}',
            200: '${palette[80]}',
            300: '${palette[70]}',
            400: '${palette[60]}',
            500: '${palette[50]}'
        }
      },\n`

const conditionSourcesBlock = `const condition${pascal}IntensitySources: Record<PaletteIntensityKey, string> = {
    10: '${palette[10]}',
    20: '${palette[20]}',
    30: '${palette[30]}',
    40: '${palette[40]}',
    50: '${palette[50]}',
    60: '${palette[60]}',
    70: '${palette[70]}',
    80: '${palette[80]}',
    90: '${palette[90]}',
    100: '${palette.normal}',
    110: '${shades[100]}',
    120: '${shades[200]}',
    130: '${shades[300]}',
}

`

const conditionColorEntry = `    condition${pascal}: createConditionPaletteColor('${paletteKey}', condition${pascal}IntensitySources, {
        text: {
            dark: {
                primary: 'text-${paletteKey}-shade-200',
                secondary: 'text-gray-550',
                extra: 'text-gray-500'
            },
        },
        background: {
            light: 'bg-${paletteKey}-10 hover:bg-${paletteKey}-20',
            lightDarker: 'bg-${paletteKey}-20 hover:bg-${paletteKey}-30',
            dark: 'bg-${paletteKey}-normal',
            extra: 'bg-${paletteKey}-20'
        },
        border: {
            light: {
                primary: 'border-${paletteKey}-30',
                secondary: 'border-${paletteKey}-40'
            }
        }
    }),
`

const mapEntry = `    ${paletteObjectKey}: colors.condition${pascal},
`

const suffixes = ['10', '20', '30', '40', '50', '60', '70', '80', '90', 'normal', 'shade-100', 'shade-200', 'shade-300']
const safelistLines = suffixes.map((suffix) =>
    `bg-${paletteKey}-${suffix} hover:bg-${paletteKey}-${suffix} group-hover:bg-${paletteKey}-${suffix} text-${paletteKey}-${suffix} hover:text-${paletteKey}-${suffix} group-hover:text-${paletteKey}-${suffix} border-${paletteKey}-${suffix} hover:border-${paletteKey}-${suffix} group-hover:border-${paletteKey}-${suffix}`
).join('\n')

const contextEntry = `    ${paletteObjectKey}: {
        background: 'bg-${paletteKey}-10 bg-opacity-[.28] hover:bg-${paletteKey}-20',
        activeBackground: 'bg-${paletteKey}-20',
        backgroundEffects: 'backdrop-blur-[8px]',
        borderPrimary: 'border-${paletteKey}-30',
        borderSecondary: 'border-${paletteKey}-40',
        connectorLine: 'bg-${paletteKey}-normal',
        iconBackground: 'bg-${paletteKey}-normal',
        iconColor: 'text-white',
        mainValue: 'text-gray-650',
        secondaryValue: 'text-${paletteKey}-shade-200',
    },
`

const snippets = {
    tailwindBlock,
    conditionSourcesBlock,
    conditionColorEntry,
    mapEntry,
    safelistLines,
    contextEntry,
}

if (printOnly) {
    console.log(`\n=== Tailwind color block ===\n${tailwindBlock}`)
    console.log(`=== Colors.ts intensity sources ===\n${conditionSourcesBlock}`)
    console.log(`=== Colors.ts condition color entry ===\n${conditionColorEntry}`)
    console.log(`=== Colors.ts safelist lines ===\n${safelistLines}\n`)
    console.log(`=== conditionCardTheme.ts card theme entry ===\n${contextEntry}`)
    process.exit(0)
}

const workspaceRoot = process.cwd()
const tailwindPath = path.join(workspaceRoot, 'tailwind.config.js')
const colorsPath = path.join(workspaceRoot, 'src/tree/Colors.ts')
const conditionCardThemePath = path.join(workspaceRoot, 'src/tree/conditionCardTheme.ts')
const touchedFiles = []

const writeIfChanged = (filePath, nextContent) => {
    const currentContent = fs.readFileSync(filePath, 'utf8')
    if (currentContent !== nextContent) {
        fs.writeFileSync(filePath, nextContent, 'utf8')
        touchedFiles.push(path.relative(workspaceRoot, filePath))
    }
}

// tailwind.config.js
{
    const original = fs.readFileSync(tailwindPath, 'utf8')
    const alreadyExists = new RegExp(`\\n\\s*['"]?${escapeRegExp(paletteKey)}['"]?:\\s*\\{`).test(original)

    if (!alreadyExists) {
        const anchors = ['\n      purple: {', '\n      gray: {', '\n    spacing: {']
        let updated = original
        let inserted = false

        for (const anchor of anchors) {
            if (updated.includes(anchor)) {
                updated = insertBeforeMarker(updated, anchor, `\n${snippets.tailwindBlock}`)
                inserted = true
                break
            }
        }

        if (!inserted) {
            throw new Error('Could not insert palette into tailwind.config.js colors object.')
        }

        writeIfChanged(tailwindPath, updated)
    }
}

// Colors.ts
{
    let updated = fs.readFileSync(colorsPath, 'utf8')

    if (!updated.includes(`const condition${pascal}IntensitySources`)) {
        updated = insertBeforeMarker(
            updated,
            'const conditionPaletteTailwindSafelist = `',
            snippets.conditionSourcesBlock
        )
    }

    if (!updated.includes(`bg-${paletteKey}-10 hover:bg-${paletteKey}-10`)) {
        const safelistRegex = /(const conditionPaletteTailwindSafelist = `)([\s\S]*?)(`\nvoid conditionPaletteTailwindSafelist)/
        const match = updated.match(safelistRegex)
        if (!match) {
            throw new Error('Could not find condition palette safelist block in Colors.ts.')
        }

        const [, prefix, body, suffix] = match
        const normalizedBody = body.endsWith('\n') ? body : `${body}\n`
        updated = updated.replace(safelistRegex, `${prefix}${normalizedBody}${snippets.safelistLines}\n${suffix}`)
    }

    updated = upsertIntoObjectLiteral({
        source: updated,
        objectStartMarker: 'export const colors: Record<string, Color> = {',
        entryRegex: new RegExp(`\\n\\s*condition${escapeRegExp(pascal)}:\\s*createConditionPaletteColor\\(`),
        entryText: snippets.conditionColorEntry,
    })

    updated = upsertIntoObjectLiteral({
        source: updated,
        objectStartMarker: 'export const conditionColorPalettes: Partial<Record<ConditionColorPalette, Color>> = {',
        entryRegex: new RegExp(`\\n\\s*['"]?${escapeRegExp(paletteKey)}['"]?:\\s*colors\\.condition${escapeRegExp(pascal)},`),
        entryText: snippets.mapEntry,
    })

    writeIfChanged(colorsPath, updated)
}

// conditionCardTheme.ts
{
    let updated = fs.readFileSync(conditionCardThemePath, 'utf8')

    updated = upsertIntoObjectLiteral({
        source: updated,
        objectStartMarkers: [
            /(?:export\s+)?const\s+defaultConditionContextCardThemeByPalette\b[^=]*=\s*\{/,
            /(?:export\s+)?const\s+conditionContextCardThemeByPalette\b[^=]*=\s*\{/,
        ],
        entryRegex: new RegExp(`\\n\\s*['"]?${escapeRegExp(paletteKey)}['"]?:\\s*\\{`),
        entryText: snippets.contextEntry,
    })

    writeIfChanged(conditionCardThemePath, updated)
}

if (touchedFiles.length === 0) {
    console.log(`Palette "${paletteKey}" already present. No file changes needed.`)
} else {
    console.log(`Palette "${paletteKey}" applied to:`)
    touchedFiles.forEach(file => console.log(`- ${file}`))
}
