'use client'

import { useState } from 'react'
import { motion, AnimatePresence } from 'motion/react'
import type { LexiconDef, LexiconProperty, LexiconSchema, ParsedField } from '@/app/lib/lexicon'
import { getDescriptionOverride, getPropertyOrder } from '@/app/data/lexicon-overrides'
import { EyeIcon, EyeOffIcon, FileIcon } from 'lucide-react'

// Client-side lexicon cache (passed from server)
let lexiconCache: Record<string, LexiconSchema> = {}

export function setLexiconCache(cache: Record<string, LexiconSchema>) {
    lexiconCache = cache
}

// Client-side ref resolution
function resolveRef(ref: string, currentSchema: LexiconSchema): LexiconDef | null {
    if (ref.startsWith('#')) {
        const defName = ref.slice(1)
        return currentSchema.defs[defName] ?? null
    }

    if (ref.includes('#')) {
        const [nsid, defName] = ref.split('#')
        const schema = lexiconCache[nsid]
        return schema?.defs[defName] ?? null
    }

    const schema = lexiconCache[ref]
    return schema?.defs.main ?? null
}

// Extended def type to handle array items
interface ExtendedLexiconDef extends LexiconDef {
    items?: {
        type: string;
        refs?: string[];
        maxLength?: number;
        maxGraphemes?: number;
    };
    minimum?: number;
    maximum?: number;
}

// Helper to get the object schema from a def (handles record types)
function getObjectSchema(def: LexiconDef): { properties?: Record<string, LexiconProperty>; required?: string[] } | null {
    // For record types, the object schema is nested inside 'record'
    if (def.type === 'record' && def.record) {
        return def.record
    }
    // For object types, properties are directly on the def
    if (def.type === 'object' && def.properties) {
        return def
    }
    return null
}

// Parse a LexiconDef into fields (for object types with properties)
function parseDefFields(
    def: LexiconDef,
    lexiconId?: string,
    defName?: string | null
): ParsedField[] {
    const schema = getObjectSchema(def)
    if (!schema?.properties) {
        return []
    }

    const requiredFields = new Set(schema.required || [])
    const propertyOrder = lexiconId ? getPropertyOrder(lexiconId, defName ?? null) : undefined

    // Sort entries by custom order if defined
    let entries = Object.entries(schema.properties)
    if (propertyOrder) {
        entries = entries.sort((a, b) => {
            const indexA = propertyOrder.indexOf(a[0])
            const indexB = propertyOrder.indexOf(b[0])
            // Properties not in order list go to the end
            const orderA = indexA === -1 ? Infinity : indexA
            const orderB = indexB === -1 ? Infinity : indexB
            return orderA - orderB
        })
    }

    return entries.map(([name, prop]) => {
        const constraints: string[] = []

        if (prop.maxLength) constraints.push(`maxLength: ${prop.maxLength}`)
        if (prop.maxGraphemes) constraints.push(`maxGraphemes: ${prop.maxGraphemes}`)
        if (prop.maxSize) {
            const bytes = prop.maxSize
            const formatted = bytes >= 1_000_000 ? `${bytes / 1_000_000}MB` : bytes >= 1_000 ? `${bytes / 1_000}KB` : `${bytes}B`
            constraints.push(`maxSize: ${formatted}`)
        }
        if (prop.accept) constraints.push(`accept: [${prop.accept.join(', ')}]`)

        if (prop.items) {
            if (prop.items.maxLength) constraints.push(`items.maxLength: ${prop.items.maxLength}`)
            if (prop.items.maxGraphemes) constraints.push(`items.maxGraphemes: ${prop.items.maxGraphemes}`)
        }

        let type = prop.type
        if (prop.format) type = `${prop.type}:${prop.format}`
        if (prop.ref) type = prop.ref
        if (prop.type === 'array' && prop.items) {
            type = `array<${prop.items.type}>`
        }

        const refs = (prop as { refs?: string[] }).refs

        // Check for description override
        const description = lexiconId
            ? getDescriptionOverride(lexiconId, defName ?? null, name) ?? prop.description
            : prop.description

        return {
            name,
            type,
            required: requiredFields.has(name),
            description,
            constraints,
            ref: prop.ref,
            refs,
        }
    })
}

// Check if def has properties (is an object type with fields)
function defHasProperties(def: LexiconDef): boolean {
    const schema = getObjectSchema(def)
    return !!schema?.properties && Object.keys(schema.properties).length > 0
}

// Get summary info for non-object defs
function getDefSummary(def: ExtendedLexiconDef): {
    type: string;
    description?: string;
    constraints: string[];
    refs?: string[]
} {
    const constraints: string[] = []
    let type = def.type

    if (def.type === 'array' && def.items) {
        type = `array<${def.items.type}>`
        if (def.items.maxLength) constraints.push(`items.maxLength: ${def.items.maxLength}`)
        if (def.items.maxGraphemes) constraints.push(`items.maxGraphemes: ${def.items.maxGraphemes}`)
    }

    if ((def as ExtendedLexiconDef).minimum !== undefined) {
        constraints.push(`minimum: ${(def as ExtendedLexiconDef).minimum}`)
    }
    if ((def as ExtendedLexiconDef).maximum !== undefined) {
        constraints.push(`maximum: ${(def as ExtendedLexiconDef).maximum}`)
    }

    return {
        type,
        description: def.description,
        constraints,
        refs: def.items?.refs,
    }
}

// Helper to parse description with inline code (backticks)
function parseInlineCode(text: string): React.ReactNode {
    const parts: React.ReactNode[] = []
    let lastIndex = 0
    const regex = /`([^`]+)`/g
    let match

    while ((match = regex.exec(text)) !== null) {
        // Add text before the match
        if (match.index > lastIndex) {
            parts.push(text.slice(lastIndex, match.index))
        }
        // Add the code part with styling
        parts.push(
            <code
                key={match.index}
                className="rounded bg-base-200 px-1 mx-0.5 font-mono text-sm text-base-content italic"
            >
                {match[1]}
            </code>
        )
        lastIndex = match.index + match[0].length
    }

    // Add remaining text
    if (lastIndex < text.length) {
        parts.push(text.slice(lastIndex))
    }

    return parts.length > 0 ? parts : text
}

function TypeBadge({ name, type, required }: { name: string; type: string; required?: boolean }) {
    return (
        <div className="flex flex-wrap items-center gap-2">
            <span className="font-mono font-semibold text-base leading-snug tracking-tight text-base-content">
                {name}
            </span>
            <span className="rounded border border-sky-200 bg-sky-100 text-sky-800 dark:border-sky-900 dark:bg-sky-950 dark:text-sky-100 px-1 py-0.5 font-mono font-medium text-sm leading-none tracking-tight">
                {type}
            </span>
            {required && (
                <span className="rounded border border-red-200 bg-red-100 text-red-800 dark:border-red-900 dark:bg-red-950 dark:text-red-100 px-1 py-0.5 font-mono font-medium text-sm leading-none tracking-tight">
                    required
                </span>
            )}
        </div>
    )
}

function RefBadge({ ref }: { ref: string }) {
    return (
        <span className="rounded border border-emerald-200 bg-emerald-100 text-emerald-800 dark:border-emerald-900 dark:bg-emerald-950 dark:text-emerald-100 px-1 py-0.5 font-mono font-medium text-sm leading-none tracking-tight">
            {ref}
        </span>
    )
}

interface ExpandableFieldProps {
    field: ParsedField;
    schema: LexiconSchema;
}

// Component to display a non-object def (array, primitive, etc.)
function DefSummaryDisplay({ def }: { def: ExtendedLexiconDef }) {
    const summary = getDefSummary(def)

    return (
        <div className="flex flex-col gap-1 p-4">
            <div className="flex flex-wrap items-center gap-2">
                <span className="rounded border border-sky-200 bg-sky-100 text-sky-800 dark:border-sky-900 dark:bg-sky-950 dark:text-sky-100 px-1 py-0.5 font-mono font-medium text-sm leading-none tracking-tight">
                    {summary.type}
                </span>
            </div>

            {/* Union refs as badges */}
            {summary.refs && summary.refs.length > 0 && (
                <div className="flex flex-wrap gap-2">
                    {summary.refs.map((ref) => (
                        <RefBadge key={ref} ref={ref} />
                    ))}
                </div>
            )}

            {summary.constraints.length > 0 && (
                <div className="flex flex-wrap gap-4 font-mono text-sm leading-none tracking-tight text-muted">
                    {summary.constraints.map((constraint) => (
                        <span key={constraint}>{constraint}</span>
                    ))}
                </div>
            )}

            {summary.description && (
                <p className="text-sm italic leading-snug tracking-tight text-muted">
                    {parseInlineCode(summary.description)}
                </p>
            )}
        </div>
    )
}

// Refs that should not be expandable
const NON_EXPANDABLE_REFS = [
    'com.atproto.repo.strongRef',
]

// Parse ref string to get lexiconId and defName for overrides
function parseRefContext(ref: string, currentSchema: LexiconSchema): { lexiconId: string; defName: string | null } {
    if (ref.startsWith('#')) {
        return { lexiconId: currentSchema.id, defName: ref.slice(1) }
    }
    if (ref.includes('#')) {
        const [nsid, defName] = ref.split('#')
        return { lexiconId: nsid, defName }
    }
    return { lexiconId: ref, defName: 'main' }
}

export function ExpandableField({ field, schema }: ExpandableFieldProps) {
    const [expanded, setExpanded] = useState(false)
    const isExpandable = !!field.ref && !NON_EXPANDABLE_REFS.includes(field.ref)

    const toggleExpand = () => {
        setExpanded(!expanded)
    }

    const resolvedDef = field.ref ? resolveRef(field.ref, schema) : null
    const refContext = field.ref ? parseRefContext(field.ref, schema) : null
    const hasNestedFields = resolvedDef ? defHasProperties(resolvedDef) : false
    const nestedFields = resolvedDef && hasNestedFields
        ? parseDefFields(resolvedDef, refContext?.lexiconId, refContext?.defName)
        : []

    // Get display name for the expand button
    const refDisplayName = field.ref?.startsWith('#') ? field.ref.slice(1) : field.ref

    return (
        <div className="flex flex-col gap-1 border-b border-border p-4 last:border-b-0">
            <TypeBadge name={field.name} type={field.type} required={field.required} />

            {/* Union refs as badges */}
            {field.refs && field.refs.length > 0 && (
                <div className="flex flex-wrap gap-2">
                    {field.refs.map((ref) => (
                        <RefBadge key={ref} ref={ref} />
                    ))}
                </div>
            )}

            {field.constraints.length > 0 && (
                <div className="flex flex-wrap gap-4 font-mono text-sm leading-none tracking-tight text-muted">
                    {field.constraints.map((constraint) => (
                        <span key={constraint}>{constraint}</span>
                    ))}
                </div>
            )}

            {field.description && (
                <p className="text-sm italic leading-snug tracking-tight text-muted">
                    {parseInlineCode(field.description)}
                </p>
            )}

            {isExpandable && (
                <div className="pt-2">
                    <div className="flex flex-col rounded-xl border border-border bg-base-200 overflow-hidden">
                        {/* Header button */}
                        <button
                            onClick={toggleExpand}
                            className={`flex items-center gap-2 px-4 py-2.5 hover:bg-base-300 transition-colors hover:cursor-pointer ${expanded ? 'border-b border-border' : ''}`}
                        >
                            <FileIcon className="size-4 text-base-content" />
                            <span className="font-medium text-base leading-snug tracking-tight text-base-content">
                                {refDisplayName}
                            </span>
                            <motion.div
                                className="ml-auto"
                                initial={false}
                                animate={{ opacity: 1 }}
                            >
                                {expanded ? (
                                    <EyeOffIcon className="size-4 text-muted" />
                                ) : (
                                    <EyeIcon className="size-4 text-muted" />
                                )}
                            </motion.div>
                        </button>

                        {/* Expandable content */}
                        <AnimatePresence initial={false}>
                            {expanded && (
                                <motion.div
                                    initial={{ height: 0, opacity: 0 }}
                                    animate={{ height: 'auto', opacity: 1 }}
                                    exit={{ height: 0, opacity: 0 }}
                                    transition={{ duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
                                    className="overflow-hidden"
                                >
                                    <div className="p-1">
                                        <div className="flex flex-col rounded-xl border border-border bg-card overflow-hidden">
                                            {hasNestedFields ? (
                                                nestedFields.map((nestedField) => (
                                                    <ExpandableField
                                                        key={nestedField.name}
                                                        field={nestedField}
                                                        schema={schema}
                                                    />
                                                ))
                                            ) : resolvedDef ? (
                                                <DefSummaryDisplay def={resolvedDef as ExtendedLexiconDef} />
                                            ) : null}
                                        </div>
                                    </div>
                                </motion.div>
                            )}
                        </AnimatePresence>
                    </div>
                </div>
            )}
        </div>
    )
}

interface LexiconContentProps {
    schema: LexiconSchema;
    fields: ParsedField[];
    allSchemas: Record<string, LexiconSchema>;
}

export function LexiconContent({ schema, fields, allSchemas }: LexiconContentProps) {
    // Initialize the lexicon cache on first render
    if (Object.keys(lexiconCache).length === 0) {
        setLexiconCache(allSchemas)
    }

    return (
        <div className="p-1">
            <div className="flex flex-col rounded-xl border border-border bg-card overflow-hidden">
                {fields.map((field) => (
                    <ExpandableField key={field.name} field={field} schema={schema} />
                ))}
            </div>
        </div>
    )
}
