export type NationGeneralColumnId =
    | 'icon'
    | 'name'
    | 'officerLevel'
    | 'expDedLv_1'
    | 'dedlevel'
    | 'explevel'
    | 'stat_1'
    | 'leadership'
    | 'strength'
    | 'intel'
    | 'troop'
    | 'goldRice_1'
    | 'gold'
    | 'rice'
    | 'city'
    | 'crew'
    | 'specials_1'
    | 'personal'
    | 'specialDomestic'
    | 'specialWar'
    | 'years_1'
    | 'belong'
    | 'killturnAndRefresh_1'
    | 'refreshScoreTotal';

export type NationGeneralGroupId = 'expDedLv' | 'stat' | 'goldRice' | 'specials' | 'years' | 'killturnAndRefresh';
export type SortDirection = 'asc' | 'desc';
export type NationGeneralViewMode = 'normal' | 'war';

export type NationGeneralColumnState = {
    colId: NationGeneralColumnId;
    width: number;
    hide: boolean;
    sort: SortDirection | null;
    sortIndex?: number;
};

export type NationGeneralGroupState = {
    groupId: NationGeneralGroupId;
    open: boolean;
};

export type NationGeneralDisplaySetting = {
    column: NationGeneralColumnState[];
    columnGroup: NationGeneralGroupState[];
};

export type NationGeneralSettingKey = [true, NationGeneralViewMode] | [false, string];

export type TextFilterOperator =
    'contains' | 'notContains' | 'equals' | 'notEqual' | 'startsWith' | 'endsWith' | 'blank' | 'notBlank';
export type NumberFilterOperator =
    | 'equals'
    | 'notEqual'
    | 'lessThan'
    | 'lessThanOrEqual'
    | 'greaterThan'
    | 'greaterThanOrEqual'
    | 'inRange'
    | 'blank'
    | 'notBlank';
export type NationGeneralFilterOperator = TextFilterOperator | NumberFilterOperator;
export type NationGeneralFilterCondition = {
    operator: NationGeneralFilterOperator;
    value: string;
    valueTo: string;
};

export const textFilterOperators: readonly { value: TextFilterOperator; label: string }[] = [
    { value: 'contains', label: 'Contains' },
    { value: 'notContains', label: 'Not contains' },
    { value: 'equals', label: 'Equals' },
    { value: 'notEqual', label: 'Not equal' },
    { value: 'startsWith', label: 'Starts with' },
    { value: 'endsWith', label: 'Ends with' },
    { value: 'blank', label: 'Blank' },
    { value: 'notBlank', label: 'Not blank' },
];

export const numberFilterOperators: readonly { value: NumberFilterOperator; label: string }[] = [
    { value: 'equals', label: 'Equals' },
    { value: 'notEqual', label: 'Not equal' },
    { value: 'lessThan', label: 'Less than' },
    { value: 'lessThanOrEqual', label: 'Less than or equals' },
    { value: 'greaterThan', label: 'Greater than' },
    { value: 'greaterThanOrEqual', label: 'Greater than or equals' },
    { value: 'inRange', label: 'In range' },
    { value: 'blank', label: 'Blank' },
    { value: 'notBlank', label: 'Not blank' },
];

export const DISPLAY_SETTINGS_KEY = 'GeneralListDisplaySetting';
export const DISPLAY_SETTINGS_VERSION = 1;
export const lastUsedSettingsKey = (role: string): string => `LastUsedSettingsKey_${role}`;

const baseColumns = (): NationGeneralColumnState[] => [
    { colId: 'icon', width: 80, hide: false, sort: null },
    { colId: 'name', width: 126, hide: false, sort: null },
    { colId: 'officerLevel', width: 70, hide: false, sort: null },
    { colId: 'expDedLv_1', width: 60, hide: false, sort: null },
    { colId: 'dedlevel', width: 70, hide: false, sort: null },
    { colId: 'explevel', width: 60, hide: false, sort: null },
    { colId: 'stat_1', width: 88, hide: false, sort: null },
    { colId: 'leadership', width: 60, hide: false, sort: null },
    { colId: 'strength', width: 60, hide: false, sort: null },
    { colId: 'intel', width: 60, hide: false, sort: null },
    { colId: 'troop', width: 90, hide: true, sort: null },
    { colId: 'goldRice_1', width: 80, hide: false, sort: null },
    { colId: 'gold', width: 70, hide: false, sort: null },
    { colId: 'rice', width: 70, hide: false, sort: null },
    { colId: 'city', width: 60, hide: true, sort: null },
    { colId: 'crew', width: 70, hide: true, sort: null },
    { colId: 'specials_1', width: 80, hide: false, sort: null },
    { colId: 'personal', width: 60, hide: false, sort: null },
    { colId: 'specialDomestic', width: 60, hide: false, sort: null },
    { colId: 'specialWar', width: 60, hide: false, sort: null },
    { colId: 'years_1', width: 60, hide: false, sort: null },
    { colId: 'belong', width: 60, hide: false, sort: null },
    { colId: 'killturnAndRefresh_1', width: 70, hide: false, sort: null },
    { colId: 'refreshScoreTotal', width: 70, hide: false, sort: null },
];

const groupState = (overrides: Partial<Record<NationGeneralGroupId, boolean>>): NationGeneralGroupState[] =>
    (['expDedLv', 'stat', 'goldRice', 'specials', 'years', 'killturnAndRefresh'] as const).map((groupId) => ({
        groupId,
        open: overrides[groupId] ?? false,
    }));

const withColumnOverrides = (
    columns: NationGeneralColumnState[],
    overrides: Partial<Record<NationGeneralColumnId, Partial<NationGeneralColumnState>>>
): NationGeneralColumnState[] =>
    columns.map((column) => ({
        ...column,
        ...overrides[column.colId],
    }));

export const defaultNationGeneralDisplaySettings: Record<NationGeneralViewMode, NationGeneralDisplaySetting> = {
    normal: {
        column: withColumnOverrides(baseColumns(), {
            troop: { hide: true },
            city: { hide: true },
            crew: { hide: true },
            refreshScoreTotal: { sort: 'desc', sortIndex: 0 },
        }),
        columnGroup: groupState({
            expDedLv: true,
            stat: true,
            goldRice: true,
            specials: false,
            years: false,
            killturnAndRefresh: true,
        }),
    },
    war: {
        column: withColumnOverrides(baseColumns(), {
            icon: { hide: true },
            officerLevel: { hide: true },
            expDedLv_1: { hide: true },
            dedlevel: { hide: true },
            explevel: { hide: true },
            troop: { hide: false },
            city: { hide: false },
            crew: { hide: false },
            specials_1: { hide: true },
            personal: { hide: true },
            specialDomestic: { hide: true },
            specialWar: { hide: true },
            years_1: { hide: true },
            belong: { hide: true },
            killturnAndRefresh_1: { hide: true },
            refreshScoreTotal: { hide: true },
        }),
        columnGroup: groupState({
            expDedLv: false,
            stat: false,
            goldRice: true,
            specials: false,
            years: false,
            killturnAndRefresh: true,
        }),
    },
};

export const cloneNationGeneralDisplaySetting = (
    setting: NationGeneralDisplaySetting
): NationGeneralDisplaySetting => ({
    column: setting.column.map((column) => ({ ...column })),
    columnGroup: setting.columnGroup.map((group) => ({ ...group })),
});

const validColumnIds = new Set<NationGeneralColumnId>(baseColumns().map((column) => column.colId));
const validGroupIds = new Set<NationGeneralGroupId>(groupState({}).map((group) => group.groupId));

const isSortDirection = (value: unknown): value is SortDirection => value === 'asc' || value === 'desc';

export const normalizeNationGeneralDisplaySetting = (raw: unknown): NationGeneralDisplaySetting | null => {
    if (!raw || typeof raw !== 'object') {
        return null;
    }
    const candidate = raw as { column?: unknown; columnGroup?: unknown };
    if (!Array.isArray(candidate.column) || !Array.isArray(candidate.columnGroup)) {
        return null;
    }

    const fallback = cloneNationGeneralDisplaySetting(defaultNationGeneralDisplaySettings.normal);
    const rawColumns = new Map<string, Record<string, unknown>>();
    for (const value of candidate.column) {
        if (!value || typeof value !== 'object') continue;
        const column = value as Record<string, unknown>;
        if (typeof column.colId === 'string' && validColumnIds.has(column.colId as NationGeneralColumnId)) {
            rawColumns.set(column.colId, column);
        }
    }
    fallback.column = fallback.column.map((column) => {
        const saved = rawColumns.get(column.colId);
        if (!saved) return column;
        return {
            ...column,
            width: typeof saved.width === 'number' && saved.width > 0 ? saved.width : column.width,
            hide: typeof saved.hide === 'boolean' ? saved.hide : column.hide,
            sort: isSortDirection(saved.sort) ? saved.sort : null,
            ...(typeof saved.sortIndex === 'number' && saved.sortIndex >= 0
                ? { sortIndex: Math.trunc(saved.sortIndex) }
                : {}),
        };
    });

    const rawGroups = new Map<string, boolean>();
    for (const value of candidate.columnGroup) {
        if (!value || typeof value !== 'object') continue;
        const group = value as Record<string, unknown>;
        if (
            typeof group.groupId === 'string' &&
            validGroupIds.has(group.groupId as NationGeneralGroupId) &&
            typeof group.open === 'boolean'
        ) {
            rawGroups.set(group.groupId, group.open);
        }
    }
    fallback.columnGroup = fallback.columnGroup.map((group) => ({
        ...group,
        open: rawGroups.get(group.groupId) ?? group.open,
    }));
    return fallback;
};

export const parseStoredDisplaySettings = (raw: string | null): Map<string, NationGeneralDisplaySetting> => {
    if (!raw) return new Map();
    try {
        const parsed = JSON.parse(raw) as { version?: unknown; settings?: unknown };
        if (parsed.version !== DISPLAY_SETTINGS_VERSION || !Array.isArray(parsed.settings)) return new Map();
        const result = new Map<string, NationGeneralDisplaySetting>();
        for (const entry of parsed.settings) {
            if (!Array.isArray(entry) || typeof entry[0] !== 'string') continue;
            const setting = normalizeNationGeneralDisplaySetting(entry[1]);
            if (setting) result.set(entry[0], setting);
        }
        return result;
    } catch {
        return new Map();
    }
};

export const serializeDisplaySettings = (settings: Map<string, NationGeneralDisplaySetting>): string =>
    JSON.stringify({
        version: DISPLAY_SETTINGS_VERSION,
        settings: [...settings.entries()],
    });

export const parseStoredSettingKey = (raw: string | null): NationGeneralSettingKey | null => {
    if (!raw) return null;
    try {
        const parsed = JSON.parse(raw) as unknown;
        if (
            !Array.isArray(parsed) ||
            parsed.length !== 2 ||
            typeof parsed[0] !== 'boolean' ||
            typeof parsed[1] !== 'string'
        ) {
            return null;
        }
        if (parsed[0]) return parsed[1] === 'normal' || parsed[1] === 'war' ? [true, parsed[1]] : null;
        return [false, parsed[1]];
    } catch {
        return null;
    }
};

const initialConsonants = 'ㄱㄲㄴㄷㄸㄹㅁㅂㅃㅅㅆㅇㅈㅉㅊㅋㅌㅍㅎ';

const hangulInitials = (value: string): string =>
    [...value]
        .map((character) => {
            const code = character.charCodeAt(0);
            if (code < 0xac00 || code > 0xd7a3) return character;
            return initialConsonants[Math.floor((code - 0xac00) / 588)] ?? character;
        })
        .join('');

const normalizeSearchText = (value: string): string => value.toLocaleLowerCase('ko-KR').replace(/\s+/g, '');

export const matchesKoreanSearch = (value: string, query: string): boolean => {
    const normalizedQuery = normalizeSearchText(query);
    if (!normalizedQuery) return true;
    const normalizedValue = normalizeSearchText(value);
    return (
        normalizedValue.includes(normalizedQuery) ||
        normalizeSearchText(hangulInitials(value)).includes(normalizedQuery)
    );
};

const normalizedSearchValues = (value: string): [string, string] => [
    normalizeSearchText(value),
    normalizeSearchText(hangulInitials(value)),
];

export const matchesTextFilterCondition = (value: string | null, condition: NationGeneralFilterCondition): boolean => {
    const blank = value === null || value === '';
    if (condition.operator === 'blank') return blank;
    if (condition.operator === 'notBlank') return !blank;

    const query = normalizeSearchText(condition.value);
    if (!query) return true;
    if (value === null) return false;
    const candidates = normalizedSearchValues(value);
    const matches = (predicate: (candidate: string) => boolean): boolean => candidates.some(predicate);
    switch (condition.operator) {
        case 'notContains':
            return !matches((candidate) => candidate.includes(query));
        case 'equals':
            return matches((candidate) => candidate === query);
        case 'notEqual':
            return !matches((candidate) => candidate === query);
        case 'startsWith':
            return matches((candidate) => candidate.startsWith(query));
        case 'endsWith':
            return matches((candidate) => candidate.endsWith(query));
        default:
            return matches((candidate) => candidate.includes(query));
    }
};

const parseFiniteNumber = (raw: string): number | null => {
    const normalized = raw.trim();
    if (!normalized) return null;
    const parsed = Number(normalized);
    return Number.isFinite(parsed) ? parsed : null;
};

export const matchesNumberFilterCondition = (
    value: number | null,
    condition: NationGeneralFilterCondition
): boolean => {
    const blank = value === null || !Number.isFinite(value);
    if (condition.operator === 'blank') return blank;
    if (condition.operator === 'notBlank') return !blank;
    if (blank) return false;

    const expected = parseFiniteNumber(condition.value);
    if (expected === null) return true;
    switch (condition.operator) {
        case 'notEqual':
            return value !== expected;
        case 'lessThan':
            return value < expected;
        case 'lessThanOrEqual':
            return value <= expected;
        case 'greaterThan':
            return value > expected;
        case 'greaterThanOrEqual':
            return value >= expected;
        case 'inRange': {
            const expectedTo = parseFiniteNumber(condition.valueTo);
            return (
                expectedTo === null ||
                (value >= Math.min(expected, expectedTo) && value <= Math.max(expected, expectedTo))
            );
        }
        default:
            return value === expected;
    }
};

export const matchesNumberSearch = (value: number | null, query: string): boolean => {
    const normalized = query.trim();
    if (!normalized) return true;
    if (value === null || !Number.isFinite(value)) return false;
    const match = /^(<=|>=|<|>|=)?\s*(-?\d+(?:\.\d+)?)$/.exec(normalized);
    if (!match) return false;
    const expected = Number(match[2]);
    switch (match[1] ?? '=') {
        case '<':
            return value < expected;
        case '<=':
            return value <= expected;
        case '>':
            return value > expected;
        case '>=':
            return value >= expected;
        default:
            return value === expected;
    }
};

export const compareGridValues = (left: string | number | null, right: string | number | null): number => {
    if (left === right) return 0;
    if (left === null) return 1;
    if (right === null) return -1;
    if (typeof left === 'number' && typeof right === 'number') return left - right;
    return String(left).localeCompare(String(right), 'ko-KR', { numeric: true });
};
