-- FUNCTION: public.buscar_personas_v2(text, text, integer)

CREATE OR REPLACE FUNCTION public.buscar_personas_v2(
	p_termino text,
	p_tipo text,
	p_limite integer DEFAULT 10)
    RETURNS TABLE(id bigint, text text, score double precision, match_type text) 
    LANGUAGE 'plpgsql'
    COST 100
    VOLATILE PARALLEL UNSAFE
    ROWS 1000

AS $BODY$
DECLARE
    v_termino_norm TEXT;
    v_tokens TEXT[];
    v_sim_threshold DOUBLE PRECISION := 0.15;  -- Más permisivo
    v_lev_threshold INTEGER := 3;              -- Permite más errores
    
    -- Pesos para ranking (ajusta según importancia)
    v_peso_nombre DOUBLE PRECISION := 1.0;
    v_peso_apellido DOUBLE PRECISION := 0.8;
    v_peso_codigo DOUBLE PRECISION := 0.6;
    v_peso_dui DOUBLE PRECISION := 0.5;
BEGIN
    -- Normalizar el término de búsqueda
    v_termino_norm := normalizar_texto(p_termino);
    
    -- Tokenizar (separar por palabras)
    v_tokens := string_to_array(v_termino_norm, ' ');
    
    -- ========================================================================
    -- BÚSQUEDA PARA NIÑOS
    -- ========================================================================
    IF upper(p_tipo) = 'NINO' THEN
        RETURN QUERY
        WITH candidatos AS (
            -- FASE 1: Filtro rápido por índice (operador %)
            SELECT
                n.id,
                p.nombres,
                p.apellidos,
                n.codigo_inscripcion,
                normalizar_texto(p.nombres) AS nombres_norm,
                normalizar_texto(p.apellidos) AS apellidos_norm,
                normalizar_texto(n.codigo_inscripcion) AS codigo_norm
            FROM ninos n
            JOIN personas p ON p.id = n.persona_id
            WHERE n.activo = true
              AND p.activo = true
              AND (
                    -- Búsqueda rápida con operador % (usa índice GIN)
                    normalizar_texto(p.nombres || ' ' || p.apellidos) % v_termino_norm
                 OR normalizar_texto(n.codigo_inscripcion) % v_termino_norm
                 OR normalizar_texto(p.nombres) % ANY(v_tokens)
                 OR normalizar_texto(p.apellidos) % ANY(v_tokens)
              )
        ),
        scoring AS (
            -- FASE 2: Cálculo preciso de scores
            SELECT
                c.id,
                (c.nombres || ' ' || c.apellidos || ' | Código: ' || c.codigo_inscripcion)::TEXT AS display_text,
                
                -- Score por similitud de nombre completo
                (similarity(c.nombres_norm || ' ' || c.apellidos_norm, v_termino_norm) * v_peso_nombre) AS score_nombre_completo,
                
                -- Score por similitud de nombres individuales
                (similarity(c.nombres_norm, v_termino_norm) * v_peso_nombre) AS score_nombres,
                
                -- Score por similitud de apellidos
                (similarity(c.apellidos_norm, v_termino_norm) * v_peso_apellido) AS score_apellidos,
                
                -- Score por código
                (similarity(c.codigo_norm, v_termino_norm) * v_peso_codigo) AS score_codigo,
                
                -- Score por Levenshtein (nombres)
                (1.0 - (levenshtein(c.nombres_norm, v_termino_norm)::DOUBLE PRECISION 
                    / GREATEST(length(c.nombres_norm), length(v_termino_norm), 1))) * v_peso_nombre AS score_lev_nombres,
                
                -- Score por tokens (cada palabra individual)
                (
                    SELECT COALESCE(MAX(
                        GREATEST(
                            similarity(c.nombres_norm, tok),
                            similarity(c.apellidos_norm, tok)
                        )
                    ), 0)
                    FROM unnest(v_tokens) tok
                ) * 0.9 AS score_tokens,
                
                -- Determinar tipo de match
                CASE
                    WHEN similarity(c.codigo_norm, v_termino_norm) >= v_sim_threshold THEN 'codigo'
                    WHEN similarity(c.nombres_norm || ' ' || c.apellidos_norm, v_termino_norm) >= v_sim_threshold THEN 'nombre_completo'
                    WHEN levenshtein(c.nombres_norm, v_termino_norm) <= v_lev_threshold THEN 'fuzzy'
                    ELSE 'token'
                END AS match_type
                
            FROM candidatos c
        )
        SELECT
            s.id,
            s.display_text,
            -- Score final: tomar el máximo de todos los métodos
            GREATEST(
                s.score_nombre_completo,
                s.score_nombres,
                s.score_apellidos,
                s.score_codigo,
                s.score_lev_nombres,
                s.score_tokens
            ) AS score,
            s.match_type
        FROM scoring s
        WHERE GREATEST(
                s.score_nombre_completo,
                s.score_nombres,
                s.score_apellidos,
                s.score_codigo,
                s.score_lev_nombres,
                s.score_tokens
              ) > 0.1  -- Umbral mínimo para aparecer en resultados
        ORDER BY score DESC
        LIMIT p_limite;
    
    -- ========================================================================
    -- BÚSQUEDA PARA ENCARGADOS
    -- ========================================================================
    ELSIF upper(p_tipo) = 'ENCARGADO' THEN
        RETURN QUERY
        WITH candidatos AS (
            SELECT
                p.id,
                p.nombres,
                p.apellidos,
                COALESCE(p.dui, '') AS dui,
                en.parentesco,
                normalizar_texto(p.nombres) AS nombres_norm,
                normalizar_texto(p.apellidos) AS apellidos_norm,
                normalizar_texto(COALESCE(p.dui, '')) AS dui_norm
            FROM encargados_nino en
            JOIN personas p ON p.id = en.persona_id
            WHERE p.activo = true
              AND (
                    normalizar_texto(p.nombres || ' ' || p.apellidos) % v_termino_norm
                 OR normalizar_texto(COALESCE(p.dui, '')) % v_termino_norm
                 OR normalizar_texto(p.nombres) % ANY(v_tokens)
                 OR normalizar_texto(p.apellidos) % ANY(v_tokens)
              )
        ),
        scoring AS (
            SELECT
                c.id,
                (c.nombres || ' ' || c.apellidos || ' (' || c.parentesco || ')')::TEXT AS display_text,
                
                (similarity(c.nombres_norm || ' ' || c.apellidos_norm, v_termino_norm) * v_peso_nombre) AS score_nombre_completo,
                (similarity(c.nombres_norm, v_termino_norm) * v_peso_nombre) AS score_nombres,
                (similarity(c.apellidos_norm, v_termino_norm) * v_peso_apellido) AS score_apellidos,
                (similarity(c.dui_norm, v_termino_norm) * v_peso_dui) AS score_dui,
                
                (1.0 - (levenshtein(c.nombres_norm, v_termino_norm)::DOUBLE PRECISION 
                    / GREATEST(length(c.nombres_norm), length(v_termino_norm), 1))) * v_peso_nombre AS score_lev_nombres,
                
                (
                    SELECT COALESCE(MAX(
                        GREATEST(
                            similarity(c.nombres_norm, tok),
                            similarity(c.apellidos_norm, tok)
                        )
                    ), 0)
                    FROM unnest(v_tokens) tok
                ) * 0.9 AS score_tokens,
                
                CASE
                    WHEN similarity(c.dui_norm, v_termino_norm) >= v_sim_threshold THEN 'dui'
                    WHEN similarity(c.nombres_norm || ' ' || c.apellidos_norm, v_termino_norm) >= v_sim_threshold THEN 'nombre_completo'
                    WHEN levenshtein(c.nombres_norm, v_termino_norm) <= v_lev_threshold THEN 'fuzzy'
                    ELSE 'token'
                END AS match_type
                
            FROM candidatos c
        )
        SELECT
            s.id,
            s.display_text,
            GREATEST(
                s.score_nombre_completo,
                s.score_nombres,
                s.score_apellidos,
                s.score_dui,
                s.score_lev_nombres,
                s.score_tokens
            ) AS score,
            s.match_type
        FROM scoring s
        WHERE GREATEST(
                s.score_nombre_completo,
                s.score_nombres,
                s.score_apellidos,
                s.score_dui,
                s.score_lev_nombres,
                s.score_tokens
              ) > 0.1
        ORDER BY score DESC
        LIMIT p_limite;

    -- ========================================================================
    -- BÚSQUEDA PARA COLABORADORES
    -- ========================================================================
    ELSIF upper(p_tipo) = 'COLABORADOR' THEN
        RETURN QUERY
        WITH candidatos AS (
            SELECT
                c.id,
                p.nombres,
                p.apellidos,
                COALESCE(p.dui, '') AS dui,
                cc.nombre as cargo,
                normalizar_texto(p.nombres) AS nombres_norm,
                normalizar_texto(p.apellidos) AS apellidos_norm,
                normalizar_texto(COALESCE(p.dui, '')) AS dui_norm
            FROM colaboradores c
            JOIN personas p ON p.id = c.persona_id
            LEFT JOIN cargos_colaboradores cc ON cc.id = c.cargo_id
            WHERE c.activo = true
              AND p.activo = true
              AND (
                    normalizar_texto(p.nombres || ' ' || p.apellidos) % v_termino_norm
                 OR normalizar_texto(COALESCE(p.dui, '')) % v_termino_norm
                 OR normalizar_texto(p.nombres) % ANY(v_tokens)
                 OR normalizar_texto(p.apellidos) % ANY(v_tokens)
              )
        ),
        scoring AS (
            SELECT
                c.id,
                (c.nombres || ' ' || c.apellidos || ' (' || COALESCE(c.cargo, 'Sin Cargo') || ')')::TEXT AS display_text,
                
                (similarity(c.nombres_norm || ' ' || c.apellidos_norm, v_termino_norm) * v_peso_nombre) AS score_nombre_completo,
                (similarity(c.nombres_norm, v_termino_norm) * v_peso_nombre) AS score_nombres,
                (similarity(c.apellidos_norm, v_termino_norm) * v_peso_apellido) AS score_apellidos,
                (similarity(c.dui_norm, v_termino_norm) * v_peso_dui) AS score_dui,
                
                (1.0 - (levenshtein(c.nombres_norm, v_termino_norm)::DOUBLE PRECISION 
                    / GREATEST(length(c.nombres_norm), length(v_termino_norm), 1))) * v_peso_nombre AS score_lev_nombres,
                
                (
                    SELECT COALESCE(MAX(
                        GREATEST(
                            similarity(c.nombres_norm, tok),
                            similarity(c.apellidos_norm, tok)
                        )
                    ), 0)
                    FROM unnest(v_tokens) tok
                ) * 0.9 AS score_tokens,
                
                CASE
                    WHEN similarity(c.dui_norm, v_termino_norm) >= v_sim_threshold THEN 'dui'
                    WHEN similarity(c.nombres_norm || ' ' || c.apellidos_norm, v_termino_norm) >= v_sim_threshold THEN 'nombre_completo'
                    WHEN levenshtein(c.nombres_norm, v_termino_norm) <= v_lev_threshold THEN 'fuzzy'
                    ELSE 'token'
                END AS match_type
                
            FROM candidatos c
        )
        SELECT
            s.id,
            s.display_text,
            GREATEST(
                s.score_nombre_completo,
                s.score_nombres,
                s.score_apellidos,
                s.score_dui,
                s.score_lev_nombres,
                s.score_tokens
            ) AS score,
            s.match_type
        FROM scoring s
        WHERE GREATEST(
                s.score_nombre_completo,
                s.score_nombres,
                s.score_apellidos,
                s.score_dui,
                s.score_lev_nombres,
                s.score_tokens
              ) > 0.1
        ORDER BY score DESC
        LIMIT p_limite;
    
    ELSE
        RAISE EXCEPTION 'Tipo no soportado: %. Use NINO, ENCARGADO o COLABORADOR', p_tipo;
    END IF;
END;
$BODY$;

ALTER FUNCTION public.buscar_personas_v2(text, text, integer)
    OWNER TO ecclesia;
