/**
 * Pattern templates — cards, visual canvas editor, rect list, drag handlers.
 */

import {
    patternEditorRects, setPatternEditorRects,
    patternEditorSelectedIdx, setPatternEditorSelectedIdx,
    patternEditorBgImage, setPatternEditorBgImage,
    patternCanvasDragMode, setPatternCanvasDragMode,
    patternCanvasDragStart, setPatternCanvasDragStart,
    patternCanvasDragOrigRect, setPatternCanvasDragOrigRect,
    patternEditorHoveredIdx, setPatternEditorHoveredIdx,
    patternEditorHoverHit, setPatternEditorHoverHit,
    PATTERN_RECT_COLORS,
    PATTERN_RECT_BORDERS,
    streamsCache,
} from '../core/state.ts';
import { API_BASE, getHeaders, fetchWithAuth, escapeHtml } from '../core/api.ts';
import { patternTemplatesCache } from '../core/state.ts';
import { t } from '../core/i18n.ts';
import { showToast, showConfirm, desktopFocus } from '../core/ui.ts';
import { Modal } from '../core/modal.ts';
import { getPictureSourceIcon, ICON_PATTERN_TEMPLATE, ICON_CLONE, ICON_EDIT } from '../core/icons.ts';
import { wrapCard } from '../core/card-colors.ts';
import { TagInput, renderTagChips } from '../core/tag-input.ts';
import { EntitySelect } from '../core/entity-palette.ts';
import type { PatternTemplate } from '../types.ts';

let _patternBgEntitySelect: EntitySelect | null = null;
let _patternTagsInput: TagInput | null = null;

// ── Auto-name ──

let _ptNameManuallyEdited = false;

function _autoGeneratePatternName() {
    if (_ptNameManuallyEdited) return;
    if ((document.getElementById('pattern-template-id') as HTMLInputElement).value) return;
    const count = patternEditorRects.length;
    const label = count > 0
        ? `${t('targets.section.pattern_templates')} · ${count} ${count === 1 ? 'rect' : 'rects'}`
        : t('targets.section.pattern_templates');
    (document.getElementById('pattern-template-name') as HTMLInputElement).value = label;
}

class PatternTemplateModal extends Modal {
    constructor() {
        super('pattern-template-modal');
    }

    snapshotValues() {
        return {
            name: (document.getElementById('pattern-template-name') as HTMLInputElement).value,
            description: (document.getElementById('pattern-template-description') as HTMLInputElement).value,
            rectangles: JSON.stringify(patternEditorRects),
            tags: JSON.stringify(_patternTagsInput ? _patternTagsInput.getValue() : []),
        };
    }

    onForceClose() {
        if (_patternTagsInput) { _patternTagsInput.destroy(); _patternTagsInput = null; }
        setPatternEditorRects([]);
        setPatternEditorSelectedIdx(-1);
        setPatternEditorBgImage(null);
        // Clean up ResizeObserver to prevent leaks
        const canvas = document.getElementById('pattern-canvas') as any;
        if (canvas?._patternResizeObserver) {
            canvas._patternResizeObserver.disconnect();
            canvas._patternResizeObserver = null;
        }
        if (canvas) canvas._patternEventsAttached = false;
    }
}

const patternModal = new PatternTemplateModal();

export function createPatternTemplateCard(pt: PatternTemplate) {
    const rectCount = (pt.rectangles || []).length;
    const desc = pt.description ? `<div class="template-description">${escapeHtml(pt.description)}</div>` : '';
    return wrapCard({
        type: 'template-card',
        dataAttr: 'data-pattern-template-id',
        id: pt.id,
        removeOnclick: `deletePatternTemplate('${pt.id}')`,
        removeTitle: t('common.delete'),
        content: `
            <div class="template-card-header">
                <span class="template-name">${ICON_PATTERN_TEMPLATE} ${escapeHtml(pt.name)}</span>
            </div>
            ${desc}
            <div class="stream-card-props">
                <span class="stream-card-prop">▭ ${rectCount} rect${rectCount !== 1 ? 's' : ''}</span>
            </div>
            ${renderTagChips(pt.tags)}`,
        actions: `
                <button class="btn btn-icon btn-secondary" onclick="clonePatternTemplate('${pt.id}')" title="${t('common.clone')}">${ICON_CLONE}</button>
                <button class="btn btn-icon btn-secondary" onclick="showPatternTemplateEditor('${pt.id}')" title="${t('common.edit')}">${ICON_EDIT}</button>`,
    });
}

export async function showPatternTemplateEditor(templateId: string | null = null, cloneData: PatternTemplate | null = null): Promise<void> {
    try {
        // Load sources for background capture
        const sources = await streamsCache.fetch().catch((): any[] => []);

        const bgSelect = document.getElementById('pattern-bg-source') as HTMLSelectElement;
        bgSelect.innerHTML = '';
        sources.forEach((s: any) => {
            const opt = document.createElement('option');
            opt.value = s.id;
            opt.textContent = s.name;
            bgSelect.appendChild(opt);
        });

        // Entity palette for background source
        if (_patternBgEntitySelect) _patternBgEntitySelect.destroy();
        if (sources.length > 0) {
            _patternBgEntitySelect = new EntitySelect({
                target: bgSelect,
                getItems: () => sources.map((s: any) => ({
                    value: s.id,
                    label: s.name,
                    icon: getPictureSourceIcon(s.stream_type),
                })),
                placeholder: t('palette.search'),
            });
        }

        setPatternEditorBgImage(null);
        setPatternEditorSelectedIdx(-1);
        setPatternCanvasDragMode(null);

        let _editorTags: string[] = [];

        if (templateId) {
            const resp = await fetch(`${API_BASE}/pattern-templates/${templateId}`, { headers: getHeaders() });
            if (!resp.ok) throw new Error('Failed to load pattern template');
            const tmpl = await resp.json();

            (document.getElementById('pattern-template-id') as HTMLInputElement).value = tmpl.id;
            (document.getElementById('pattern-template-name') as HTMLInputElement).value = tmpl.name;
            (document.getElementById('pattern-template-description') as HTMLInputElement).value = tmpl.description || '';
            (document.getElementById('pattern-template-modal-title') as HTMLElement).innerHTML = `${ICON_PATTERN_TEMPLATE} ${t('pattern.edit')}`;
            setPatternEditorRects((tmpl.rectangles || []).map(r => ({ ...r })));
            _editorTags = tmpl.tags || [];
        } else if (cloneData) {
            (document.getElementById('pattern-template-id') as HTMLInputElement).value = '';
            (document.getElementById('pattern-template-name') as HTMLInputElement).value = (cloneData.name || '') + ' (Copy)';
            (document.getElementById('pattern-template-description') as HTMLInputElement).value = cloneData.description || '';
            (document.getElementById('pattern-template-modal-title') as HTMLElement).innerHTML = `${ICON_PATTERN_TEMPLATE} ${t('pattern.add')}`;
            setPatternEditorRects((cloneData.rectangles || []).map(r => ({ ...r })));
            _editorTags = cloneData.tags || [];
        } else {
            (document.getElementById('pattern-template-id') as HTMLInputElement).value = '';
            (document.getElementById('pattern-template-name') as HTMLInputElement).value = '';
            (document.getElementById('pattern-template-description') as HTMLInputElement).value = '';
            (document.getElementById('pattern-template-modal-title') as HTMLElement).innerHTML = `${ICON_PATTERN_TEMPLATE} ${t('pattern.add')}`;
            setPatternEditorRects([]);
        }

        // Tags
        if (_patternTagsInput) { _patternTagsInput.destroy(); _patternTagsInput = null; }
        _patternTagsInput = new TagInput(document.getElementById('pattern-tags-container'), { placeholder: t('tags.placeholder') });
        _patternTagsInput.setValue(_editorTags);

        // Auto-name wiring
        _ptNameManuallyEdited = !!(templateId || cloneData);
        (document.getElementById('pattern-template-name') as HTMLElement).oninput = () => { _ptNameManuallyEdited = true; };
        if (!templateId && !cloneData) _autoGeneratePatternName();

        patternModal.snapshot();

        renderPatternRectList();
        renderPatternCanvas();
        _attachPatternCanvasEvents();

        patternModal.open();

        (document.getElementById('pattern-template-error') as HTMLElement).style.display = 'none';
        setTimeout(() => desktopFocus(document.getElementById('pattern-template-name')), 100);
    } catch (error) {
        console.error('Failed to open pattern template editor:', error);
        showToast(t('pattern.error.editor_open_failed'), 'error');
    }
}

export function isPatternEditorDirty(): boolean {
    return patternModal.isDirty();
}

export async function closePatternTemplateModal(): Promise<void> {
    await patternModal.close();
}

export function forceClosePatternTemplateModal(): void {
    patternModal.forceClose();
}

export async function savePatternTemplate(): Promise<void> {
    const templateId = (document.getElementById('pattern-template-id') as HTMLInputElement).value;
    const name = (document.getElementById('pattern-template-name') as HTMLInputElement).value.trim();
    const description = (document.getElementById('pattern-template-description') as HTMLInputElement).value.trim();

    if (!name) {
        patternModal.showError(t('pattern.error.required'));
        return;
    }

    const payload = {
        name,
        rectangles: patternEditorRects.map(r => ({
            name: r.name, x: r.x, y: r.y, width: r.width, height: r.height,
        })),
        description: description || null,
        tags: _patternTagsInput ? _patternTagsInput.getValue() : [],
    };

    try {
        let response;
        if (templateId) {
            response = await fetchWithAuth(`/pattern-templates/${templateId}`, {
                method: 'PUT', body: JSON.stringify(payload),
            });
        } else {
            response = await fetchWithAuth('/pattern-templates', {
                method: 'POST', body: JSON.stringify(payload),
            });
        }

        if (!response.ok) {
            const err = await response.json();
            throw new Error(err.detail || 'Failed to save');
        }

        showToast(templateId ? t('pattern.updated') : t('pattern.created'), 'success');
        patternTemplatesCache.invalidate();
        patternModal.forceClose();
        // Use window.* to avoid circular import with targets.js
        if (typeof window.loadTargetsTab === 'function') window.loadTargetsTab();
    } catch (error) {
        if (error.isAuth) return;
        console.error('Error saving pattern template:', error);
        patternModal.showError(error.message);
    }
}

export async function clonePatternTemplate(templateId: string): Promise<void> {
    try {
        const templates = await patternTemplatesCache.fetch();
        const tmpl = templates.find((t: any) => t.id === templateId);
        if (!tmpl) throw new Error('Pattern template not found');
        showPatternTemplateEditor(null, tmpl);
    } catch (error) {
        if (error.isAuth) return;
        showToast(t('pattern.error.clone_failed'), 'error');
    }
}

export async function deletePatternTemplate(templateId: string): Promise<void> {
    const confirmed = await showConfirm(t('pattern.delete.confirm'));
    if (!confirmed) return;

    try {
        const response = await fetchWithAuth(`/pattern-templates/${templateId}`, {
            method: 'DELETE',
        });
        if (response.ok) {
            showToast(t('pattern.deleted'), 'success');
            patternTemplatesCache.invalidate();
            if (typeof window.loadTargetsTab === 'function') window.loadTargetsTab();
        } else {
            const error = await response.json();
            showToast(error.detail || t('pattern.error.delete_failed'), 'error');
        }
    } catch (error) {
        if (error.isAuth) return;
        showToast(t('pattern.error.delete_failed'), 'error');
    }
}

// ----- Pattern rect list (precise coordinate inputs) -----

export function renderPatternRectList(): void {
    const container = document.getElementById('pattern-rect-list');
    if (!container) return;

    if (patternEditorRects.length === 0) {
        container.innerHTML = `<div class="kc-rect-empty">${t('pattern.rect.empty')}</div>`;
        return;
    }

    container.innerHTML = patternEditorRects.map((rect, i) => `
        <div class="pattern-rect-row${i === patternEditorSelectedIdx ? ' selected' : ''}" onclick="selectPatternRect(${i})">
            <input type="text" value="${escapeHtml(rect.name)}" placeholder="${t('pattern.rect.name')}" onchange="updatePatternRect(${i}, 'name', this.value)" onclick="event.stopPropagation()">
            <input type="number" value="${rect.x.toFixed(2)}" min="0" max="1" step="0.01" onchange="updatePatternRect(${i}, 'x', parseFloat(this.value)||0)" onclick="event.stopPropagation()">
            <input type="number" value="${rect.y.toFixed(2)}" min="0" max="1" step="0.01" onchange="updatePatternRect(${i}, 'y', parseFloat(this.value)||0)" onclick="event.stopPropagation()">
            <input type="number" value="${rect.width.toFixed(2)}" min="0.01" max="1" step="0.01" onchange="updatePatternRect(${i}, 'width', parseFloat(this.value)||0.01)" onclick="event.stopPropagation()">
            <input type="number" value="${rect.height.toFixed(2)}" min="0.01" max="1" step="0.01" onchange="updatePatternRect(${i}, 'height', parseFloat(this.value)||0.01)" onclick="event.stopPropagation()">
            <button type="button" class="pattern-rect-remove-btn" onclick="event.stopPropagation(); removePatternRect(${i})" title="${t('pattern.rect.remove')}">&#x2715;</button>
        </div>
    `).join('');
}

export function selectPatternRect(index: number): void {
    setPatternEditorSelectedIdx(patternEditorSelectedIdx === index ? -1 : index);
    renderPatternRectList();
    renderPatternCanvas();
}

export function updatePatternRect(index: number, field: string, value: string | number): void {
    if (index < 0 || index >= patternEditorRects.length) return;
    patternEditorRects[index][field] = value;
    // Clamp coordinates
    if (field !== 'name') {
        const r = patternEditorRects[index];
        r.x = Math.max(0, Math.min(1 - r.width, r.x));
        r.y = Math.max(0, Math.min(1 - r.height, r.y));
        r.width = Math.max(0.01, Math.min(1, r.width));
        r.height = Math.max(0.01, Math.min(1, r.height));
    }
    renderPatternCanvas();
}

export function addPatternRect(): void {
    const name = `Zone ${patternEditorRects.length + 1}`;
    // Inherit size from selected rect, or default to 30%
    let w = 0.3, h = 0.3;
    if (patternEditorSelectedIdx >= 0 && patternEditorSelectedIdx < patternEditorRects.length) {
        const sel = patternEditorRects[patternEditorSelectedIdx];
        w = sel.width;
        h = sel.height;
    }
    const x = Math.min(0.5 - w / 2, 1 - w);
    const y = Math.min(0.5 - h / 2, 1 - h);
    patternEditorRects.push({ name, x: Math.max(0, x), y: Math.max(0, y), width: w, height: h });
    setPatternEditorSelectedIdx(patternEditorRects.length - 1);
    renderPatternRectList();
    renderPatternCanvas();
    _autoGeneratePatternName();
}

export function deleteSelectedPatternRect(): void {
    if (patternEditorSelectedIdx < 0 || patternEditorSelectedIdx >= patternEditorRects.length) return;
    patternEditorRects.splice(patternEditorSelectedIdx, 1);
    setPatternEditorSelectedIdx(-1);
    renderPatternRectList();
    renderPatternCanvas();
    _autoGeneratePatternName();
}

export function removePatternRect(index: number): void {
    patternEditorRects.splice(index, 1);
    if (patternEditorSelectedIdx === index) setPatternEditorSelectedIdx(-1);
    else if (patternEditorSelectedIdx > index) setPatternEditorSelectedIdx(patternEditorSelectedIdx - 1);
    renderPatternRectList();
    renderPatternCanvas();
    _autoGeneratePatternName();
}

// ----- Pattern Canvas Visual Editor -----

export function renderPatternCanvas(): void {
    const canvas = document.getElementById('pattern-canvas') as HTMLCanvasElement;
    if (!canvas) return;
    const ctx = canvas.getContext('2d')!;
    const w = canvas.width;
    const h = canvas.height;

    // Clear
    ctx.clearRect(0, 0, w, h);

    // Draw background image or grid
    if (patternEditorBgImage) {
        ctx.drawImage(patternEditorBgImage, 0, 0, w, h);
    } else {
        // Draw subtle grid
        ctx.fillStyle = 'rgba(128,128,128,0.05)';
        ctx.fillRect(0, 0, w, h);
        ctx.strokeStyle = 'rgba(128,128,128,0.15)';
        ctx.lineWidth = 1;
        const dpr = window.devicePixelRatio || 1;
        const gridStep = 80 * dpr;
        const colsCount = Math.max(2, Math.round(w / gridStep));
        const rowsCount = Math.max(2, Math.round(h / gridStep));
        for (let gx = 0; gx <= colsCount; gx++) {
            const x = Math.round(gx * w / colsCount) + 0.5;
            ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, h); ctx.stroke();
        }
        for (let gy = 0; gy <= rowsCount; gy++) {
            const y = Math.round(gy * h / rowsCount) + 0.5;
            ctx.beginPath(); ctx.moveTo(0, y); ctx.lineTo(w, y); ctx.stroke();
        }
    }

    // Draw rectangles
    const dpr = window.devicePixelRatio || 1;
    patternEditorRects.forEach((rect, i) => {
        const rx = rect.x * w;
        const ry = rect.y * h;
        const rw = rect.width * w;
        const rh = rect.height * h;
        const colorIdx = i % PATTERN_RECT_COLORS.length;
        const isSelected = (i === patternEditorSelectedIdx);
        const isHovered = (i === patternEditorHoveredIdx) && !patternCanvasDragMode;
        const isDragging = (i === patternEditorSelectedIdx) && !!patternCanvasDragMode;

        // Fill
        ctx.fillStyle = PATTERN_RECT_COLORS[colorIdx];
        ctx.fillRect(rx, ry, rw, rh);
        if (isHovered || isDragging) {
            ctx.fillStyle = 'rgba(255,255,255,0.08)';
            ctx.fillRect(rx, ry, rw, rh);
        }

        // Border
        ctx.strokeStyle = PATTERN_RECT_BORDERS[colorIdx];
        ctx.lineWidth = isSelected ? 3 : isHovered ? 2.5 : 1.5;
        ctx.strokeRect(rx, ry, rw, rh);

        // Edge highlight
        let edgeDir: string | null = null;
        if (isDragging && patternCanvasDragMode?.startsWith('resize-')) {
            edgeDir = patternCanvasDragMode.replace('resize-', '');
        } else if (isHovered && patternEditorHoverHit && patternEditorHoverHit !== 'move') {
            edgeDir = patternEditorHoverHit;
        }

        if (edgeDir) {
            ctx.save();
            ctx.strokeStyle = '#fff';
            ctx.lineWidth = 3 * dpr;
            ctx.shadowColor = 'rgba(76,175,80,0.7)';
            ctx.shadowBlur = 6 * dpr;
            ctx.beginPath();
            if (edgeDir.includes('n')) { ctx.moveTo(rx, ry); ctx.lineTo(rx + rw, ry); }
            if (edgeDir.includes('s')) { ctx.moveTo(rx, ry + rh); ctx.lineTo(rx + rw, ry + rh); }
            if (edgeDir.includes('w')) { ctx.moveTo(rx, ry); ctx.lineTo(rx, ry + rh); }
            if (edgeDir.includes('e')) { ctx.moveTo(rx + rw, ry); ctx.lineTo(rx + rw, ry + rh); }
            ctx.stroke();
            ctx.restore();
        }

        // Name label
        ctx.fillStyle = '#fff';
        ctx.font = `${12 * dpr}px sans-serif`;
        ctx.shadowColor = 'rgba(0,0,0,0.7)';
        ctx.shadowBlur = 3;
        ctx.fillText(rect.name, rx + 4 * dpr, ry + 14 * dpr);
        ctx.shadowBlur = 0;

        // Delete button on hovered or selected rects (not during drag)
        if ((isHovered || isSelected) && !patternCanvasDragMode) {
            const btnR = 9 * dpr;
            const btnCx = rx + rw - btnR - 2 * dpr;
            const btnCy = ry + btnR + 2 * dpr;
            ctx.beginPath();
            ctx.arc(btnCx, btnCy, btnR, 0, Math.PI * 2);
            ctx.fillStyle = 'rgba(0,0,0,0.6)';
            ctx.fill();
            ctx.strokeStyle = 'rgba(255,255,255,0.5)';
            ctx.lineWidth = 1;
            ctx.stroke();
            const cross = btnR * 0.5;
            ctx.beginPath();
            ctx.moveTo(btnCx - cross, btnCy - cross);
            ctx.lineTo(btnCx + cross, btnCy + cross);
            ctx.moveTo(btnCx + cross, btnCy - cross);
            ctx.lineTo(btnCx - cross, btnCy + cross);
            ctx.strokeStyle = '#fff';
            ctx.lineWidth = 1.5 * dpr;
            ctx.stroke();
        }
    });

    // Draw "add rectangle" placement buttons (4 corners + center) when not dragging
    if (!patternCanvasDragMode) {
        const abR = 12 * dpr;
        const abMargin = 18 * dpr;
        const addBtnPositions = [
            { cx: abMargin,     cy: abMargin },
            { cx: w - abMargin, cy: abMargin },
            { cx: w / 2,        cy: h / 2 },
            { cx: abMargin,     cy: h - abMargin },
            { cx: w - abMargin, cy: h - abMargin },
        ];
        addBtnPositions.forEach(pos => {
            ctx.beginPath();
            ctx.arc(pos.cx, pos.cy, abR, 0, Math.PI * 2);
            ctx.fillStyle = 'rgba(255,255,255,0.10)';
            ctx.fill();
            ctx.strokeStyle = 'rgba(255,255,255,0.25)';
            ctx.lineWidth = 1;
            ctx.stroke();
            const pl = abR * 0.5;
            ctx.beginPath();
            ctx.moveTo(pos.cx - pl, pos.cy);
            ctx.lineTo(pos.cx + pl, pos.cy);
            ctx.moveTo(pos.cx, pos.cy - pl);
            ctx.lineTo(pos.cx, pos.cy + pl);
            ctx.strokeStyle = 'rgba(255,255,255,0.5)';
            ctx.lineWidth = 1.5 * dpr;
            ctx.stroke();
        });
    }
}

// Placement button positions (relative 0-1 coords for where the new rect is anchored)
const _ADD_BTN_ANCHORS = [
    { ax: 0, ay: 0 },
    { ax: 1, ay: 0 },
    { ax: 0.5, ay: 0.5 },
    { ax: 0, ay: 1 },
    { ax: 1, ay: 1 },
];

function _hitTestAddButtons(mx: number, my: number, w: number, h: number): number {
    const dpr = window.devicePixelRatio || 1;
    const abR = 12 * dpr;
    const abMargin = 18 * dpr;
    const positions = [
        { cx: abMargin, cy: abMargin },
        { cx: w - abMargin, cy: abMargin },
        { cx: w / 2, cy: h / 2 },
        { cx: abMargin, cy: h - abMargin },
        { cx: w - abMargin, cy: h - abMargin },
    ];
    for (let i = 0; i < positions.length; i++) {
        const dx = mx - positions[i].cx, dy = my - positions[i].cy;
        if (dx * dx + dy * dy <= (abR + 3 * dpr) * (abR + 3 * dpr)) return i;
    }
    return -1;
}

function _addRectAtAnchor(anchorIdx: number): void {
    const anchor = _ADD_BTN_ANCHORS[anchorIdx];
    const name = `Zone ${patternEditorRects.length + 1}`;
    let rw = 0.3, rh = 0.3;
    if (patternEditorSelectedIdx >= 0 && patternEditorSelectedIdx < patternEditorRects.length) {
        const sel = patternEditorRects[patternEditorSelectedIdx];
        rw = sel.width;
        rh = sel.height;
    }
    let rx = anchor.ax - rw * anchor.ax;
    let ry = anchor.ay - rh * anchor.ay;
    rx = Math.max(0, Math.min(1 - rw, rx));
    ry = Math.max(0, Math.min(1 - rh, ry));
    patternEditorRects.push({ name, x: rx, y: ry, width: rw, height: rh });
    setPatternEditorSelectedIdx(patternEditorRects.length - 1);
    renderPatternRectList();
    renderPatternCanvas();
}

// Hit-test a point against a rect's edges/corners.
const _EDGE_THRESHOLD = 8;

function _hitTestRect(mx: number, my: number, r: { x: number; y: number; width: number; height: number }, w: number, h: number): string | null {
    const rx = r.x * w, ry = r.y * h, rw = r.width * w, rh = r.height * h;
    const dpr = window.devicePixelRatio || 1;
    const thr = _EDGE_THRESHOLD * dpr;

    const nearLeft = Math.abs(mx - rx) <= thr;
    const nearRight = Math.abs(mx - (rx + rw)) <= thr;
    const nearTop = Math.abs(my - ry) <= thr;
    const nearBottom = Math.abs(my - (ry + rh)) <= thr;
    const inHRange = mx >= rx - thr && mx <= rx + rw + thr;
    const inVRange = my >= ry - thr && my <= ry + rh + thr;

    if (nearTop && nearLeft && inHRange && inVRange) return 'nw';
    if (nearTop && nearRight && inHRange && inVRange) return 'ne';
    if (nearBottom && nearLeft && inHRange && inVRange) return 'sw';
    if (nearBottom && nearRight && inHRange && inVRange) return 'se';

    if (nearTop && inHRange) return 'n';
    if (nearBottom && inHRange) return 's';
    if (nearLeft && inVRange) return 'w';
    if (nearRight && inVRange) return 'e';

    if (mx >= rx && mx <= rx + rw && my >= ry && my <= ry + rh) return 'move';
    return null;
}

const _DIR_CURSORS = {
    'nw': 'nwse-resize', 'se': 'nwse-resize',
    'ne': 'nesw-resize', 'sw': 'nesw-resize',
    'n': 'ns-resize', 's': 'ns-resize',
    'e': 'ew-resize', 'w': 'ew-resize',
    'move': 'grab',
};

function _hitTestDeleteButton(mx: number, my: number, rect: { x: number; y: number; width: number; height: number }, w: number, h: number): boolean {
    const dpr = window.devicePixelRatio || 1;
    const btnR = 9 * dpr;
    const rx = rect.x * w, ry = rect.y * h, rw = rect.width * w;
    const btnCx = rx + rw - btnR - 2 * dpr;
    const btnCy = ry + btnR + 2 * dpr;
    const dx = mx - btnCx, dy = my - btnCy;
    return (dx * dx + dy * dy) <= (btnR + 2 * dpr) * (btnR + 2 * dpr);
}

function _patternCanvasDragMove(e: MouseEvent | { clientX: number; clientY: number }): void {
    if (!patternCanvasDragMode || patternEditorSelectedIdx < 0) return;
    const canvas = document.getElementById('pattern-canvas') as HTMLCanvasElement;
    const w = canvas.width;
    const h = canvas.height;
    const canvasRect = canvas.getBoundingClientRect();
    const scaleX = w / canvasRect.width;
    const scaleY = h / canvasRect.height;
    const mx = (e.clientX - canvasRect.left) * scaleX;
    const my = (e.clientY - canvasRect.top) * scaleY;

    const dx = (mx - patternCanvasDragStart!.mx!) / w;
    const dy = (my - patternCanvasDragStart!.my!) / h;
    const orig = patternCanvasDragOrigRect!;
    const r = patternEditorRects[patternEditorSelectedIdx];

    if (patternCanvasDragMode === 'move') {
        r.x = Math.max(0, Math.min(1 - r.width, orig.x + dx));
        r.y = Math.max(0, Math.min(1 - r.height, orig.y + dy));
    } else if (patternCanvasDragMode?.startsWith('resize-')) {
        const dir = patternCanvasDragMode!.replace('resize-', '');
        let nx = orig.x, ny = orig.y, nw = orig.width, nh = orig.height;
        if (dir.includes('w')) { nx = orig.x + dx; nw = orig.width - dx; }
        if (dir.includes('e')) { nw = orig.width + dx; }
        if (dir.includes('n')) { ny = orig.y + dy; nh = orig.height - dy; }
        if (dir.includes('s')) { nh = orig.height + dy; }
        if (nw < 0.02) { nw = 0.02; if (dir.includes('w')) nx = orig.x + orig.width - 0.02; }
        if (nh < 0.02) { nh = 0.02; if (dir.includes('n')) ny = orig.y + orig.height - 0.02; }
        nx = Math.max(0, Math.min(1 - nw, nx));
        ny = Math.max(0, Math.min(1 - nh, ny));
        nw = Math.min(1, nw);
        nh = Math.min(1, nh);
        r.x = nx; r.y = ny; r.width = nw; r.height = nh;
    }
    renderPatternCanvas();
}

function _patternCanvasDragEnd(e: MouseEvent): void {
    window.removeEventListener('mousemove', _patternCanvasDragMove);
    window.removeEventListener('mouseup', _patternCanvasDragEnd);
    setPatternCanvasDragMode(null);
    setPatternCanvasDragStart(null);
    setPatternCanvasDragOrigRect(null);

    // Recalculate hover at current mouse position
    const canvas = document.getElementById('pattern-canvas') as HTMLCanvasElement;
    if (canvas) {
        const w = canvas.width;
        const h = canvas.height;
        const canvasRect = canvas.getBoundingClientRect();
        const scaleX = w / canvasRect.width;
        const scaleY = h / canvasRect.height;
        const mx = (e.clientX - canvasRect.left) * scaleX;
        const my = (e.clientY - canvasRect.top) * scaleY;
        let cursor = 'default';
        let newHoverIdx = -1;
        let newHoverHit: string | null = null;
        if (e.clientX >= canvasRect.left && e.clientX <= canvasRect.right &&
            e.clientY >= canvasRect.top && e.clientY <= canvasRect.bottom) {
            for (let i = patternEditorRects.length - 1; i >= 0; i--) {
                const hit = _hitTestRect(mx, my, patternEditorRects[i], w, h);
                if (hit) {
                    cursor = _DIR_CURSORS[hit] || 'default';
                    newHoverIdx = i;
                    newHoverHit = hit;
                    break;
                }
            }
        }
        canvas.style.cursor = cursor;
        setPatternEditorHoveredIdx(newHoverIdx);
        setPatternEditorHoverHit(newHoverHit);
    }
    renderPatternRectList();
    renderPatternCanvas();
}

function _attachPatternCanvasEvents(): void {
    const canvas = document.getElementById('pattern-canvas') as any;
    if (!canvas || canvas._patternEventsAttached) return;
    canvas._patternEventsAttached = true;

    canvas.addEventListener('mousedown', _patternCanvasMouseDown);
    canvas.addEventListener('mousemove', _patternCanvasMouseMove);
    canvas.addEventListener('mouseleave', _patternCanvasMouseLeave);

    // Touch support
    canvas.addEventListener('touchstart', (e) => {
        e.preventDefault();
        const touch = e.touches[0];
        _patternCanvasMouseDown(_touchToMouseEvent(canvas, touch, 'mousedown'));
    }, { passive: false });
    canvas.addEventListener('touchmove', (e) => {
        e.preventDefault();
        const touch = e.touches[0];
        if (patternCanvasDragMode) {
            _patternCanvasDragMove({ clientX: touch.clientX, clientY: touch.clientY });
        } else {
            _patternCanvasMouseMove(_touchToMouseEvent(canvas, touch, 'mousemove'));
        }
    }, { passive: false });
    canvas.addEventListener('touchend', () => {
        if (patternCanvasDragMode) {
            window.removeEventListener('mousemove', _patternCanvasDragMove);
            window.removeEventListener('mouseup', _patternCanvasDragEnd);
            setPatternCanvasDragMode(null);
            setPatternCanvasDragStart(null);
            setPatternCanvasDragOrigRect(null);
            setPatternEditorHoveredIdx(-1);
            setPatternEditorHoverHit(null);
            renderPatternRectList();
            renderPatternCanvas();
        }
    });

    // Resize observer
    const container = canvas.parentElement;
    if (container && typeof ResizeObserver !== 'undefined') {
        const ro = new ResizeObserver(() => {
            const rect = container.getBoundingClientRect();
            const dpr = window.devicePixelRatio || 1;
            canvas.width = Math.round(rect.width * dpr);
            canvas.height = Math.round(rect.height * dpr);
            renderPatternCanvas();
        });
        ro.observe(container);
        canvas._patternResizeObserver = ro;
    }
}

function _touchToMouseEvent(canvas: HTMLCanvasElement, touch: Touch, type: string): { type: string; offsetX: number; offsetY: number; preventDefault: () => void } {
    const rect = canvas.getBoundingClientRect();
    return { type, offsetX: touch.clientX - rect.left, offsetY: touch.clientY - rect.top, preventDefault: () => {} };
}

function _patternCanvasMouseDown(e: MouseEvent | { offsetX?: number; offsetY?: number; clientX?: number; clientY?: number; preventDefault: () => void }): void {
    const canvas = document.getElementById('pattern-canvas') as HTMLCanvasElement;
    const w = canvas.width;
    const h = canvas.height;
    const rect = canvas.getBoundingClientRect();
    const scaleX = w / rect.width;
    const scaleY = h / rect.height;
    const mx = (e.offsetX !== undefined ? e.offsetX : (e.clientX ?? 0) - rect.left) * scaleX;
    const my = (e.offsetY !== undefined ? e.offsetY : (e.clientY ?? 0) - rect.top) * scaleY;

    // Check delete button on hovered or selected rects first
    for (const idx of [patternEditorHoveredIdx, patternEditorSelectedIdx]) {
        if (idx >= 0 && idx < patternEditorRects.length) {
            if (_hitTestDeleteButton(mx, my, patternEditorRects[idx], w, h)) {
                patternEditorRects.splice(idx, 1);
                if (patternEditorSelectedIdx === idx) setPatternEditorSelectedIdx(-1);
                else if (patternEditorSelectedIdx > idx) setPatternEditorSelectedIdx(patternEditorSelectedIdx - 1);
                setPatternEditorHoveredIdx(-1);
                setPatternEditorHoverHit(null);
                renderPatternRectList();
                renderPatternCanvas();
                return;
            }
        }
    }

    // Test all rects; selected rect takes priority so it stays interactive
    // even when overlapping with others.
    const selIdx = patternEditorSelectedIdx;
    const testOrder: number[] = [];
    if (selIdx >= 0 && selIdx < patternEditorRects.length) testOrder.push(selIdx);
    for (let i = patternEditorRects.length - 1; i >= 0; i--) {
        if (i !== selIdx) testOrder.push(i);
    }
    for (const i of testOrder) {
        const r = patternEditorRects[i];
        const hit = _hitTestRect(mx, my, r, w, h);
        if (!hit) continue;

        setPatternEditorSelectedIdx(i);
        setPatternCanvasDragStart({ mx, my });
        setPatternCanvasDragOrigRect({ ...r });

        if (hit === 'move') {
            setPatternCanvasDragMode('move');
            canvas.style.cursor = 'grabbing';
        } else {
            setPatternCanvasDragMode(`resize-${hit}`);
            canvas.style.cursor = _DIR_CURSORS[hit] || 'default';
        }

        // Capture mouse at window level for drag
        window.addEventListener('mousemove', _patternCanvasDragMove);
        window.addEventListener('mouseup', _patternCanvasDragEnd);
        e.preventDefault();

        renderPatternRectList();
        renderPatternCanvas();
        return;
    }

    // Check placement "+" buttons (corners + center)
    const addIdx = _hitTestAddButtons(mx, my, w, h);
    if (addIdx >= 0) {
        _addRectAtAnchor(addIdx);
        return;
    }

    // Click on empty space — deselect
    setPatternEditorSelectedIdx(-1);
    setPatternCanvasDragMode(null);
    canvas.style.cursor = 'default';
    renderPatternRectList();
    renderPatternCanvas();
}

function _patternCanvasMouseMove(e: MouseEvent | { offsetX?: number; offsetY?: number; clientX?: number; clientY?: number }): void {
    if (patternCanvasDragMode) return;

    const canvas = document.getElementById('pattern-canvas') as HTMLCanvasElement;
    const w = canvas.width;
    const h = canvas.height;
    const rect = canvas.getBoundingClientRect();
    const scaleX = w / rect.width;
    const scaleY = h / rect.height;
    const mx = (e.offsetX !== undefined ? e.offsetX : (e.clientX ?? 0) - rect.left) * scaleX;
    const my = (e.offsetY !== undefined ? e.offsetY : (e.clientY ?? 0) - rect.top) * scaleY;

    let cursor = 'default';
    let newHoverIdx = -1;
    let newHoverHit: string | null = null;
    // Selected rect takes priority for hover so edges stay reachable under overlaps
    const selIdx = patternEditorSelectedIdx;
    const hoverOrder: number[] = [];
    if (selIdx >= 0 && selIdx < patternEditorRects.length) hoverOrder.push(selIdx);
    for (let i = patternEditorRects.length - 1; i >= 0; i--) {
        if (i !== selIdx) hoverOrder.push(i);
    }
    for (const i of hoverOrder) {
        const hit = _hitTestRect(mx, my, patternEditorRects[i], w, h);
        if (hit) {
            cursor = _DIR_CURSORS[hit] || 'default';
            newHoverIdx = i;
            newHoverHit = hit;
            break;
        }
    }
    canvas.style.cursor = cursor;
    if (newHoverIdx !== patternEditorHoveredIdx || newHoverHit !== patternEditorHoverHit) {
        setPatternEditorHoveredIdx(newHoverIdx);
        setPatternEditorHoverHit(newHoverHit);
        renderPatternCanvas();
    }
}

function _patternCanvasMouseLeave(): void {
    if (patternCanvasDragMode) return;
    if (patternEditorHoveredIdx !== -1) {
        setPatternEditorHoveredIdx(-1);
        setPatternEditorHoverHit(null);
        renderPatternCanvas();
    }
}

export async function capturePatternBackground(): Promise<void> {
    const sourceId = (document.getElementById('pattern-bg-source') as HTMLSelectElement).value;
    if (!sourceId) {
        showToast(t('pattern.source_for_bg.none'), 'error');
        return;
    }

    try {
        const resp = await fetch(`${API_BASE}/picture-sources/${sourceId}/test`, {
            method: 'POST',
            headers: getHeaders(),
            body: JSON.stringify({ capture_duration: 0 }),
        });
        if (!resp.ok) throw new Error('Failed to capture');
        const data = await resp.json();

        if (data.full_capture && data.full_capture.full_image) {
            const img = new Image();
            img.onload = () => {
                setPatternEditorBgImage(img);
                renderPatternCanvas();
            };
            img.src = data.full_capture.full_image;
        }
    } catch (error) {
        console.error('Failed to capture background:', error);
        showToast(t('pattern.error.capture_bg_failed'), 'error');
    }
}
