/**
 * Graph connection editing — maps edge types to API fields and endpoints.
 * Supports creating, changing, and detaching connections via the graph editor.
 */

import { fetchWithAuth } from './api.ts';
import {
    streamsCache, colorStripSourcesCache, valueSourcesCache,
    audioSourcesCache, outputTargetsCache, automationsCacheObj,
} from './state.ts';

/* ── Types ────────────────────────────────────────────────────── */

interface ConnectionEntry {
    targetKind: string;
    field: string;
    sourceKind: string;
    edgeType: string;
    endpoint?: string;
    cache?: { invalidate(): void };
    nested?: boolean;
}

interface CompatibleInput {
    targetKind: string;
    field: string;
    edgeType: string;
}

/**
 * Connection map: for each (targetKind, field) pair, defines:
 *   - sourceKind: which entity kind(s) can be the source
 *   - edgeType: the edge type for this connection
 *   - endpoint: the API endpoint pattern (use {id} for target ID)
 *   - cache: the DataCache to invalidate after update
 *   - nested: true if this field is inside a nested structure (not editable via drag)
 */
const CONNECTION_MAP: ConnectionEntry[] = [
    // Picture sources
    { targetKind: 'picture_source', field: 'capture_template_id', sourceKind: 'capture_template', edgeType: 'template', endpoint: '/picture-sources/{id}', cache: streamsCache },
    { targetKind: 'picture_source', field: 'source_stream_id', sourceKind: 'picture_source', edgeType: 'picture', endpoint: '/picture-sources/{id}', cache: streamsCache },
    { targetKind: 'picture_source', field: 'postprocessing_template_id', sourceKind: 'pp_template', edgeType: 'template', endpoint: '/picture-sources/{id}', cache: streamsCache },

    // Audio sources
    { targetKind: 'audio_source', field: 'audio_template_id', sourceKind: 'audio_template', edgeType: 'audio', endpoint: '/audio-sources/{id}', cache: audioSourcesCache },
    { targetKind: 'audio_source', field: 'audio_source_id', sourceKind: 'audio_source', edgeType: 'audio', endpoint: '/audio-sources/{id}', cache: audioSourcesCache },

    // Value sources
    { targetKind: 'value_source', field: 'audio_source_id', sourceKind: 'audio_source', edgeType: 'audio', endpoint: '/value-sources/{id}', cache: valueSourcesCache },
    { targetKind: 'value_source', field: 'picture_source_id', sourceKind: 'picture_source', edgeType: 'picture', endpoint: '/value-sources/{id}', cache: valueSourcesCache },

    // Color strip sources
    { targetKind: 'color_strip_source', field: 'picture_source_id', sourceKind: 'picture_source', edgeType: 'picture', endpoint: '/color-strip-sources/{id}', cache: colorStripSourcesCache },
    { targetKind: 'color_strip_source', field: 'audio_source_id', sourceKind: 'audio_source', edgeType: 'audio', endpoint: '/color-strip-sources/{id}', cache: colorStripSourcesCache },
    { targetKind: 'color_strip_source', field: 'clock_id', sourceKind: 'sync_clock', edgeType: 'clock', endpoint: '/color-strip-sources/{id}', cache: colorStripSourcesCache },

    // Output targets
    { targetKind: 'output_target', field: 'device_id', sourceKind: 'device', edgeType: 'device', endpoint: '/output-targets/{id}', cache: outputTargetsCache },
    { targetKind: 'output_target', field: 'color_strip_source_id', sourceKind: 'color_strip_source', edgeType: 'colorstrip', endpoint: '/output-targets/{id}', cache: outputTargetsCache },
    { targetKind: 'output_target', field: 'brightness_value_source_id', sourceKind: 'value_source', edgeType: 'value', endpoint: '/output-targets/{id}', cache: outputTargetsCache },
    { targetKind: 'output_target', field: 'picture_source_id', sourceKind: 'picture_source', edgeType: 'picture', endpoint: '/output-targets/{id}', cache: outputTargetsCache },

    // Automations
    { targetKind: 'automation', field: 'scene_preset_id', sourceKind: 'scene_preset', edgeType: 'scene', endpoint: '/automations/{id}', cache: automationsCacheObj },
    { targetKind: 'automation', field: 'deactivation_scene_preset_id', sourceKind: 'scene_preset', edgeType: 'scene', endpoint: '/automations/{id}', cache: automationsCacheObj },

    // ── Nested fields (not drag-editable in V1) ──
    { targetKind: 'color_strip_source', field: 'layer.source_id', sourceKind: 'color_strip_source', edgeType: 'colorstrip', nested: true },
    { targetKind: 'color_strip_source', field: 'layer.brightness_source_id', sourceKind: 'value_source', edgeType: 'value', nested: true },
    { targetKind: 'color_strip_source', field: 'zone.source_id', sourceKind: 'color_strip_source', edgeType: 'colorstrip', nested: true },
    { targetKind: 'color_strip_source', field: 'calibration.picture_source_id', sourceKind: 'picture_source', edgeType: 'picture', nested: true },
    { targetKind: 'output_target', field: 'settings.pattern_template_id', sourceKind: 'pattern_template', edgeType: 'template', nested: true },
    { targetKind: 'output_target', field: 'settings.brightness_value_source_id', sourceKind: 'value_source', edgeType: 'value', nested: true },
    { targetKind: 'scene_preset', field: 'target_id', sourceKind: 'output_target', edgeType: 'scene', nested: true },
];

/**
 * Check if an edge (by field name) is editable via drag-connect.
 */
export function isEditableEdge(field: string): boolean {
    const entry = CONNECTION_MAP.find(c => c.field === field);
    return entry ? !entry.nested : false;
}

/**
 * Find the connection mapping for a given target kind and source kind.
 * Returns the matching entry (or entries) from CONNECTION_MAP.
 */
export function findConnection(targetKind: string, sourceKind: string, edgeType?: string): ConnectionEntry[] {
    return CONNECTION_MAP.filter(c =>
        !c.nested &&
        c.targetKind === targetKind &&
        c.sourceKind === sourceKind &&
        (!edgeType || c.edgeType === edgeType)
    );
}

/**
 * Find compatible input port fields for a given source kind.
 * Returns array of { targetKind, field, edgeType }.
 */
export function getCompatibleInputs(sourceKind: string): CompatibleInput[] {
    return CONNECTION_MAP
        .filter(c => !c.nested && c.sourceKind === sourceKind)
        .map(c => ({ targetKind: c.targetKind, field: c.field, edgeType: c.edgeType }));
}

/**
 * Find the connection entry for a specific edge (by target kind and field).
 */
export function getConnectionByField(targetKind: string, field: string): ConnectionEntry | undefined {
    return CONNECTION_MAP.find(c => c.targetKind === targetKind && c.field === field && !c.nested);
}

/**
 * Update a connection: set the reference field on the target entity.
 * @param {string} targetId - The target entity's ID
 * @param {string} targetKind - The target entity's kind
 * @param {string} field - The field name to update
 * @param {string|null} newSourceId - New source ID, or '' to detach
 * @returns {Promise<boolean>} success
 */
export async function updateConnection(targetId: string, targetKind: string, field: string, newSourceId: string | null): Promise<boolean> {
    const entry = CONNECTION_MAP.find(c => c.targetKind === targetKind && c.field === field && !c.nested);
    if (!entry) return false;

    const url = entry.endpoint!.replace('{id}', targetId);
    const body = { [field]: newSourceId };

    try {
        const resp = await fetchWithAuth(url, {
            method: 'PUT',
            body: JSON.stringify(body),
        });
        if (!resp.ok) return false;
        // Invalidate the relevant cache so data refreshes
        if (entry.cache) entry.cache.invalidate();
        return true;
    } catch {
        return false;
    }
}

/**
 * Detach a connection (set field to null via empty-string sentinel).
 */
export async function detachConnection(targetId: string, targetKind: string, field: string): Promise<boolean> {
    return updateConnection(targetId, targetKind, field, '');
}

export { CONNECTION_MAP };
