create or replace procedure prc_26AI_NL2SQL ( 
                                           p_app_user in varchar2, 
                                           p_prompt in clob,
                                           p_profile in varchar2, 
                                           p_action in varchar2 default 'narrate',
                                           p_limit_rows in number default 100,
                                           p_force_runsql in varchar2 default 'N',
                                           p_result out clob
                                           )
as
/*

  Criado por: fernando.leal@oracle.com
  Data: Oct/2025
  Objetivo: demonstrar casos de uso do Oracle AI Database 26ai

  v1 - rotina para controle de regras ao SelectAI - leal

begin
    dbms_cloud_ai.create_profile(
        profile_name => 'PROF_CENSO_V1',
        attributes =>
            '{"provider": "oci",
            "credential_name": "OCI_CRED",
            "oci_compartment_id":  "ocid1.compartment.oc1..xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            "region": "us-chicago-1",
			"model": "meta.llama-4-maverick-17b-128e-instruct-fp8", 
            "oci_apiformat": "GENERIC",  
            "object_list_mode": "automated",
            "object_list": [
                 {"owner": "AICHAT1", "name": "TB_26AI_CENSO"}
             ],
            "enforce_object_list": true,
            "constraints":"true",
            "comments": true,
			"annotations": true, 
            "temperature": 0.1
            }'
        );
end;


*/
 v_response clob;
 v_narrate clob;
 v_pre_prompt_geral clob;
 v_pre_prompt_contexto clob;
 v_contexto clob;

 v_categoria varchar2(200);
 v_profile varchar2(200);

 v_estimated_rows number;
 --
 -- Limite de linhas que pode ser narrado
 --
 v_limit_rows number := p_limit_rows;
 --
 -- caso numero de linhas seja muito alto (definido por v_limit_rows), a acao narrate sera alterada para runwsql automaticamente
 --
 v_force_runsql varchar2(1) := upper(p_force_runsql);
 v_dt_start TIMESTAMP ;
 v_runsql clob;
begin

 v_dt_start := current_timestamp;

 v_pre_prompt_geral := 'Sempre utilize alias para referencias nome da tabela. Nunca cite owner.';

	--
	-- Ajustar e definir as categorias existentes. As categorias irao definir pre prompts mais especificos
	--
	SELECT DBMS_CLOUD_AI.GENERATE(prompt =>  'Com base na pergunta: "' || p_prompt || '", defina qual a melhor categoria para ela: "cidades","estados","população","censo","outros". 
	                                          A resposta deve ser em JSON, e deve seguir este exemplo:  { "categoria": "exemplo" }. APENAS ESTE JSON DEVE SER RETORNADO.',
											  profile_name => p_profile,
											  action       => 'chat') response
	 INTO v_contexto; 

    begin
     SELECT jt.categoria
       INTO v_categoria
       FROM JSON_TABLE(
                        replace(replace(v_contexto,'```',''),'json',''), 
                        '$[*]' COLUMNS (
                            categoria VARCHAR2(50) PATH '$.categoria'
                        ) 
                     ) jt; 
    exception
       when others then
          p_result := v_contexto;
    end;

    dbms_output.put_line('v_categoria: ' || v_categoria);                         

    if v_categoria in ('cidades','estados','população','censo') then

        v_pre_prompt_contexto := 'Filtros para nome de estados sempre devem ser com 2 caracteres em MAISUCULO. Exemplo: Minas Gerais como MG, Amazonas como AM, Pará como PA'; 

         -- pode haver definicao de varios profiles, sendo que cada um estara associado a uma categoria especifica. Neste exemplo, herda da propria chamada 
        v_profile := 'PROF_CENSO_V3';

	else

        p_result := 'O prompt informado não faz referência aos dados que podem ser retornados. Contexto: ' || v_categoria;

	end if;

     dbms_output.put_line('v_profile: ' || v_profile);  

    --
    -- deve ter havido uma categorizacao da questao com associacao a um profile do SelectAI
    --
    if v_profile is not null then

            -- resposta do showsql para analise do comando
        SELECT DBMS_CLOUD_AI.GENERATE(prompt =>  v_pre_prompt_geral || ' ' || v_pre_prompt_contexto || ' ' || p_prompt,
                                  profile_name => v_profile,
                                  action       => 'showsql') response
          INTO v_response; 

        if upper(v_response) like 'SELECT%' or  upper(v_response) like 'WITH%'  then  

            -- estimativa do numero de linahs retornadas
            -- baseado em estatisticas das tabelas e indices 
            BEGIN
                    EXECUTE IMMEDIATE 'EXPLAIN PLAN SET STATEMENT_ID = ''plan_selectai'' FOR ' || v_response;

                    SELECT MAX(cardinality) 
                    INTO v_estimated_rows
                    FROM plan_table
                    WHERE id = 0 
                    AND STATEMENT_ID = 'plan_selectai';
            EXCEPTION
                   WHEN OTHERS THEN
                      v_estimated_rows := 0;
            END;

            dbms_output.put_line('v_response: ' || v_response);   
            dbms_output.put_line('v_estimated_rows: ' || v_estimated_rows);  
            dbms_output.put_line('v_limit_rows: ' || v_limit_rows);  
            dbms_output.put_line('v_force_runsql: ' || v_force_runsql);   

                -- validacoes  
               -- if instr(upper(v_response),'WHERE') = 0 and instr(upper(v_response),'GROUP') = 0 then

                    --p_result := 'O prompt informado não tem nenhum tipo de filtro. Procure adicionar algum critério, como filtros por data, empresa, região, SKU, etc';

                if v_estimated_rows > v_limit_rows then

                --
                -- colocar recurso pra trazer dados parcisi (ate X linhas )
                --

                    if lower(p_action) = 'narrate' and v_force_runsql = 'Y' then

                        -- chamada principal do SelectAI  
                        BEGIN
                            SELECT DBMS_CLOUD_AI.GENERATE(prompt =>  v_pre_prompt_geral || ' ' || v_pre_prompt_contexto || ' ' || p_prompt,
                                                  profile_name => v_profile,
                                                  action       => 'runsql') response
                            INTO v_narrate; 
                        EXCEPTION
                          WHEN NO_DATA_FOUND THEN
                            v_narrate := 'Desculpe, mas não há informações específicas sobre "' || p_prompt || '". Há regras de acesso definidas em seu perfil.';
                        END;

                         p_result := v_narrate;

                    else

                          p_result := 'Estimativa de linhas retornadas: ' || v_estimated_rows || '. Procure adicionar algum critério, como filtros por data, empresa, região, SKU, etc';

                    end if;

                else

                    if p_action <> 'showsql' then
                        -- chamada principal do SelectAI  
                        BEGIN
                          SELECT DBMS_CLOUD_AI.GENERATE(prompt =>  v_pre_prompt_geral || ' ' || v_pre_prompt_contexto || ' ' || p_prompt,
                                                  profile_name => v_profile,
                                                  action       => p_action) response
                          INTO v_narrate; 

                          /*
                          Feedback is available only on Oracle AI Database 26ai. 
                          You can use it alongside the existing Select AI actions: runsql, showsql, and explainsql. 
                          Ensure that your AI profile is configured for NL2SQL generation and not RAG. 
                          */
                          if p_action <> 'runsql' then
                                SELECT DBMS_CLOUD_AI.GENERATE(prompt =>  v_pre_prompt_geral || ' ' || v_pre_prompt_contexto || ' ' || p_prompt,
                                                  profile_name => v_profile,
                                                  action       => 'runsql') response
                                INTO v_runsql; 
                          end if;  

                        EXCEPTION
                          WHEN NO_DATA_FOUND THEN
                            v_narrate := 'Desculpe, mas não há informações específicas sobre "' || p_prompt || '". Há regras de acesso definidas em seu perfil.';
                        END;

                        p_result := v_narrate;

                    else

                        p_result := v_response; -- sql ja gerado


                    end if;


                end if;   

        else

             p_result := 'Não foi possível gerar uma consulta para atender sua questão: ' || v_response;

        end if;	-- select 

    end if; -- v_profile



    if p_result is not null then

                --
                -- log
                --
                insert into TB_26AI_CENSO_HISTORY(USERNAME,
                               DT,
                               PROMPT,
                               RESPONSE, 
                               PROFILE,
                               NARRATE,
                               DT_START,
                               DT_END,
                               PROMPT_CHARS,
                               RESPONSE_CHARS
							  )
                 values( lower( p_app_user ) , 
                   sysdate , 
                   p_prompt ,
                   v_response ,
                   v_profile,
                   --case 
                    -- when lower(p_action) = 'narrate' then
                        p_result,
                   -- end,
                   v_dt_start,
                   current_timestamp,
                   DBMS_LOB.GETLENGTH(p_prompt),
                   case 
                     when lower(p_action) = 'narrate' then
                        DBMS_LOB.GETLENGTH(v_response) + DBMS_LOB.GETLENGTH(p_result) 
                     else
                       DBMS_LOB.GETLENGTH(p_result)
                   end                   
				  ) ;      
            commit;
     end if; -- save do p_result

     dbms_output.put_line('p_result: ' || p_result);  

end;
/