
-- DROP TYPES (se existirem)
DROP TYPE product_result_tab;
DROP TYPE product_result;
/

-- Criação de um tipo de tabela para retorno da função
CREATE TYPE product_result AS OBJECT (
                                                       code VARCHAR2(50),
                                                       description VARCHAR2(4000),
                                                       similarity NUMBER
                                                   );

CREATE OR REPLACE TYPE product_result_tab AS TABLE OF product_result;
/

-- Função de busca fonética e por palavras-chave
CREATE OR REPLACE FUNCTION fn_advanced_search(p_termos IN VARCHAR2)
    RETURN product_result_tab PIPELINED
AS
    v_termos SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST();
    v_token VARCHAR2(1000);
    v_description VARCHAR2(4000);
    v_score NUMBER;
    v_dummy NUMBER;
BEGIN
    -- Dividir os termos da busca
    FOR i IN 1..REGEXP_COUNT(p_termos, '\S+') LOOP
            v_termos.EXTEND;
            v_termos(i) := LOWER(REGEXP_SUBSTR(p_termos, '\S+', 1, i));
        END LOOP;

    -- Loop pelos products
    FOR prod IN (SELECT code, description FROM products) LOOP
            v_description := LOWER(prod.description);
            v_score := 0;

            -- Avaliar cada termo da busca
            FOR i IN 1..v_termos.COUNT LOOP
                    v_token := v_termos(i);

                    -- 3 pontos se encontrar diretamente
                    IF v_description LIKE '%' || v_token || '%' THEN
                        v_score := v_score + 3;
                    ELSE
                        -- 2 pontos se foneticamente similar
                        BEGIN
                            SELECT 1 INTO v_dummy FROM dual
                            WHERE SOUNDEX(v_token) IN (
                                SELECT SOUNDEX(REGEXP_SUBSTR(v_description, '\w+', 1, LEVEL))
                                FROM dual
                                CONNECT BY LEVEL <= REGEXP_COUNT(v_description, '\w+')
                            );
                            v_score := v_score + 2;
                        EXCEPTION
                            WHEN NO_DATA_FOUND THEN NULL;
                        END;

                        -- 1 ponto se similar por escrita
                        BEGIN
                            SELECT 1 INTO v_dummy FROM dual
                            WHERE EXISTS (
                                SELECT 1
                                FROM (
                                         SELECT REGEXP_SUBSTR(v_description, '\w+', 1, LEVEL) AS palavra
                                         FROM dual
                                         CONNECT BY LEVEL <= REGEXP_COUNT(v_description, '\w+')
                                     )
                                WHERE UTL_MATCH.EDIT_DISTANCE(palavra, v_token) <= 2
                            );
                            v_score := v_score + 1;
                        EXCEPTION
                            WHEN NO_DATA_FOUND THEN NULL;
                        END;
                    END IF;
                END LOOP;

            -- Só retorna se houver ao menos algum match
            IF v_score > 0 THEN
                PIPE ROW(product_result(prod.code, prod.description, v_score));
            END IF;
        END LOOP;

    RETURN;
END;
/

-- Grant para execução, se necessário:
GRANT EXECUTE ON fn_advanced_search TO PUBLIC;


-- Testes
SELECT *
FROM TABLE(fn_advanced_search('harry poter askaban'))
ORDER BY similarity DESC;
