import React from 'react';
import { SObjectField, SObject } from '../types';
import { COMMON_FIELDS } from '../core/constants/CommonFields';
import { decodeHtmlEntities } from '../core/utils/htmlUtils';
import { RegistryBase } from '../core/shared/utils';
import { normalizeFieldInputValue } from '../core/metadata/fieldInputValue';
import { getRecordValue } from '../core/utils/recordValue';
import { buildFieldDisplayValueKey } from '../core/metadata/fieldSurfacePolicy';
import { DateInput, DateTimeInput } from '../components/ui/DateInput';


// --- Prop Type Definitions ---

export interface FieldRendererProps {
    field: SObjectField;
    value: unknown;
    onNavigate?: (obj: string, id: string) => void;
    record?: SObject;
    variant?: 'table' | 'detail';
    policyScope?: string;
    /**
     * Optional callback supplied to renderers that own their own edit UX.
     * The callback updates the parent record's local state so the renderer
     * re-renders immediately after the user picks a new value via its modal,
     * without waiting for a full record reload. Most renderers ignore this.
     */
    onUpdateField?: (newValue: unknown) => void;
}

export type FieldInputRenderContext = 'edit' | 'filter';

export interface FieldInputProps {
    field: SObjectField;
    value: unknown;
    onChange: (value: unknown, selectedRecord?: SObject) => void;
    context?: FieldInputRenderContext;
    disabled?: boolean;
    placeholder?: string;
    required?: boolean;
    options?: string[];
    onKeyDown?: (e: React.KeyboardEvent) => void;
    autoFocus?: boolean;
    inputId?: string;
    ariaInvalid?: boolean;
    ariaDescribedBy?: string;
}

const toDateInputValue = (value: unknown): string => {
    if (value === null || value === undefined || value === '') return '';
    const raw = String(value);
    // Only trim ISO date(-time) strings; other shapes (relative-date tokens
    // like LAST_N_DAYS:7 in filter rows) pass through untruncated so the user
    // sees the real stored value.
    return /^\d{4}-\d{2}-\d{2}/.test(raw) ? raw.slice(0, 10) : raw;
};

const toDateTimeLocalInputValue = (value: unknown): string => {
    if (value === null || value === undefined || value === '') return '';
    const raw = String(value);
    const backendUtc = raw.match(/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})(?::(\d{2})(?:\.\d+)?)?$/);
    const normalized = backendUtc ? `${backendUtc[1]}T${backendUtc[2]}:${backendUtc[3] ?? '00'}Z` : raw;
    const parsed = new Date(normalized);
    if (Number.isNaN(parsed.getTime())) return raw;

    const local = new Date(parsed.getTime() - parsed.getTimezoneOffset() * 60_000);
    return local.toISOString().slice(0, 16);
};

const normalizeFilterDateTimeValue = (value: string): string => {
    if (!value) return '';
    const parsed = new Date(value);
    if (Number.isNaN(parsed.getTime())) return value;
    return parsed.toISOString().replace(/\.\d{3}Z$/, 'Z');
};

const fieldInputValue = (props: FieldInputProps): string => {
    if (props.field.type === 'date') return toDateInputValue(props.value);
    if (props.field.type === 'datetime') return toDateTimeLocalInputValue(props.value);
    return (props.value as string) || '';
};

const normalizeFieldInputChange = (props: FieldInputProps, value: string): unknown => {
    if (props.context === 'filter' && props.field.type === 'datetime') {
        return normalizeFilterDateTimeValue(value);
    }
    return normalizeFieldInputValue(props.field, value);
};

const DEFAULT_FIELD_INPUT_BASE_CLASSES =
    'w-full border rounded-md px-3 py-2 text-sm focus:ring-2 outline-none disabled:bg-slate-100 disabled:text-slate-500';

// When a field is flagged invalid (aria-invalid), the control itself turns red
// — matching the create/edit form and the picklist/lookup inputs — so the
// offending field reads at a glance on inline edit, not only via a parent ring.
// The error message is still owned by the surrounding surface, not the input.
const fieldInputClasses = (ariaInvalid?: boolean): string =>
    `${DEFAULT_FIELD_INPUT_BASE_CLASSES} ${ariaInvalid
        ? 'border-red-400 focus:ring-red-500'
        : 'border-slate-300 focus:ring-blue-500'}`;

const DefaultFieldInput: React.FC<FieldInputProps> = (props) => {
    let inputType = 'text';
    if (props.field.type === 'double' || props.field.type === 'currency' || props.field.type === 'percent') inputType = 'number';

    // Read-only fields check
    if (props.context !== 'filter' && (props.field.name === COMMON_FIELDS.CREATED_DATE || props.field.name === COMMON_FIELDS.LAST_MODIFIED_DATE)) {
        return <span className="text-slate-700">{String(props.value ?? '')}</span>;
    }

    if (props.field.type === 'date') {
        return (
            <DateInput
                value={toDateInputValue(props.value)}
                onChange={(newValue) => props.onChange(normalizeFieldInputChange(props, newValue))}
                disabled={props.disabled}
                className={fieldInputClasses(props.ariaInvalid)}
                onKeyDown={props.onKeyDown}
                autoFocus={props.autoFocus}
                required={props.required}
                id={props.inputId}
                aria-invalid={props.ariaInvalid || undefined}
                aria-describedby={props.ariaDescribedBy}
            />
        );
    }
    if (props.field.type === 'datetime') {
        return (
            <DateTimeInput
                value={toDateTimeLocalInputValue(props.value)}
                onChange={(newValue) => props.onChange(normalizeFieldInputChange(props, newValue))}
                disabled={props.disabled}
                className={fieldInputClasses(props.ariaInvalid)}
                onKeyDown={props.onKeyDown}
                autoFocus={props.autoFocus}
                required={props.required}
                id={props.inputId}
                aria-invalid={props.ariaInvalid || undefined}
                aria-describedby={props.ariaDescribedBy}
            />
        );
    }

    return (
        <input
            type={inputType}
            className={fieldInputClasses(props.ariaInvalid)}
            required={props.required}
            onChange={(e) => props.onChange(normalizeFieldInputChange(props, e.target.value))}
            value={fieldInputValue(props)}
            placeholder={props.placeholder}
            disabled={props.disabled}
            step={props.field.type === 'currency' || props.field.type === 'percent' ? '0.01' : undefined}
            onKeyDown={props.onKeyDown}
            autoFocus={props.autoFocus}
            id={props.inputId}
            aria-invalid={props.ariaInvalid || undefined}
            aria-describedby={props.ariaDescribedBy}
        />
    );
};

// --- Registry Implementation ---

// Concrete implementation for internal use
class SimpleRegistry<T> extends RegistryBase<T> { }

class UIRegistryClass {
    private renderers: RegistryBase<React.FC<FieldRendererProps>>;
    private rendererOverrides: Map<string, React.FC<FieldRendererProps>> = new Map();
    private inputs: RegistryBase<React.FC<FieldInputProps>>;

    constructor() {
        this.renderers = new SimpleRegistry<React.FC<FieldRendererProps>>({
            validator: (key, value) => typeof value === 'function' || typeof value === 'object' ? true : `Renderer "${key}" must be a component`,
            enableEvents: true
        });

        this.inputs = new SimpleRegistry<React.FC<FieldInputProps>>({
            validator: (key, value) => typeof value === 'function' || typeof value === 'object' ? true : `Input "${key}" must be a component`,
            enableEvents: true
        });
    }

    registerFieldRenderer(type: string, component: React.FC<FieldRendererProps>) {
        this.renderers.register(type, component);
    }

    hasFieldRenderer(type: string): boolean {
        return this.renderers.has(type);
    }

    fieldRendererKeys(): string[] {
        return this.renderers.getKeys();
    }

    /**
     * registerFieldRendererByName installs a renderer that overrides the
     * type-keyed default for one specific (objectApiName, fieldApiName) pair.
     * Used by features that need a custom UI for a single field.
     *
     * Lookup precedence (in getFieldRendererForField): name override → type
     * default → fallback. Names are matched case-insensitively.
     */
    registerFieldRendererByName(objectApiName: string, fieldApiName: string, component: React.FC<FieldRendererProps>) {
        this.rendererOverrides.set(this.overrideKey(objectApiName, fieldApiName), component);
    }

    /**
     * getFieldRendererForField looks up a renderer by (object, field) name
     * first, then falls back to the type-keyed default. Use this everywhere
     * a renderer is invoked from a record-aware context (detail page, related
     * lists, inline edit). Plain getFieldRenderer remains for type-only
     * contexts.
     */
    getFieldRendererForField(objectApiName: string | undefined, fieldApiName: string | undefined, type: string): React.FC<FieldRendererProps> {
        if (objectApiName && fieldApiName) {
            const override = this.rendererOverrides.get(this.overrideKey(objectApiName, fieldApiName));
            if (override) return override;
        }
        return this.getFieldRenderer(type);
    }

    /**
     * hasFieldRendererOverride reports whether the given (object, field) pair
     * has a name-keyed renderer registered. Used by InlineEditField to suppress
     * its own inline-edit chrome (pencil, text input) when a custom renderer
     * is responsible for its own edit UX.
     */
    hasFieldRendererOverride(objectApiName: string | undefined, fieldApiName: string | undefined): boolean {
        if (!objectApiName || !fieldApiName) return false;
        return this.rendererOverrides.has(this.overrideKey(objectApiName, fieldApiName));
    }

    private overrideKey(objectApiName: string, fieldApiName: string): string {
        return `${objectApiName.toLowerCase()}.${fieldApiName.toLowerCase()}`;
    }

    getFieldRenderer(type: string): React.FC<FieldRendererProps> {
        return this.renderers.get(type) || (({ value, record, field, variant, policyScope }) => {
            // Try explicit display value first
            if (record && field) {
                const displayVal = getRecordValue(record, buildFieldDisplayValueKey(field.name, policyScope));
                if (displayVal !== undefined && displayVal !== null) {
                    const text = decodeHtmlEntities(String(displayVal));
                    return (
                        <span
                            className={variant === 'detail' ? 'block min-w-0 truncate text-slate-700' : 'text-slate-700'}
                            title={variant === 'detail' ? text : undefined}
                        >
                            {text}
                        </span>
                    );
                }
            }

            // Try to resolve value if undefined using case-insensitive lookup
            let displayValue = value;
            if (displayValue === undefined && record && field) {
                displayValue = getRecordValue(record, field.name);
            }

            // Display em-dash placeholder for null/undefined/empty values
            if (displayValue === null || displayValue === undefined || displayValue === '') {
                return <span className="text-slate-400">—</span>;
            }
            const text = decodeHtmlEntities(String(displayValue));
            return (
                <span
                    className={variant === 'detail' ? 'block min-w-0 truncate text-slate-700' : 'text-slate-700'}
                    title={variant === 'detail' ? text : undefined}
                >
                    {text}
                </span>
            );
        });
    }

    registerFieldInput(type: string, component: React.FC<FieldInputProps>) {
        this.inputs.register(type, component);
    }

    hasFieldInput(type: string): boolean {
        return this.inputs.has(type);
    }

    fieldInputKeys(): string[] {
        return this.inputs.getKeys();
    }

    getFieldInput(type: string): React.FC<FieldInputProps> {
        return this.inputs.get(type) || DefaultFieldInput;
    }

    clear() {
        this.renderers.clear();
        this.inputs.clear();
        this.rendererOverrides.clear();
    }

}

export const UIRegistry = new UIRegistryClass();
