-- SGMP PoC: validated table/view and column annotation API
-- Issue: #734

CREATE OR REPLACE FUNCTION sgmp_set_annotation(
    p_schema_name    IN VARCHAR2,
    p_target_kind    IN VARCHAR2,
    p_object_name    IN VARCHAR2,
    p_column_name    IN VARCHAR2 DEFAULT NULL,
    p_change_text    IN VARCHAR2 DEFAULT NULL,
    p_annotation_name IN VARCHAR2 DEFAULT 'AI_GUIDANCE'
) RETURN VARCHAR2
AUTHID DEFINER
IS
    l_schema_name     VARCHAR2(128);
    l_object_name     VARCHAR2(128);
    l_column_name     VARCHAR2(128);
    l_annotation_name VARCHAR2(128);
    l_target_kind     VARCHAR2(20);
    l_object_type     VARCHAR2(30);
    l_exists          PLS_INTEGER := 0;
    l_action          VARCHAR2(20);
    l_sql             VARCHAR2(32767);

    FUNCTION simple_name(p_value VARCHAR2, p_label VARCHAR2) RETURN VARCHAR2 IS
        l_value VARCHAR2(128) := UPPER(TRIM(p_value));
    BEGIN
        IF l_value IS NULL OR NOT REGEXP_LIKE(l_value, '^[A-Z][A-Z0-9_$#]{0,127}$') THEN
            RAISE_APPLICATION_ERROR(-20001, p_label || ' has an invalid format.');
        END IF;
        RETURN l_value;
    END;

    FUNCTION qname(p_value VARCHAR2) RETURN VARCHAR2 IS
    BEGIN
        RETURN DBMS_ASSERT.ENQUOTE_NAME(p_value, FALSE);
    END;

    FUNCTION literal(p_value VARCHAR2) RETURN VARCHAR2 IS
    BEGIN
        RETURN DBMS_ASSERT.ENQUOTE_LITERAL(p_value);
    END;
BEGIN
    l_schema_name := simple_name(p_schema_name, 'schema_name');
    l_object_name := simple_name(p_object_name, 'object_name');
    l_target_kind := UPPER(TRIM(p_target_kind));
    IF l_target_kind NOT IN ('TABLE', 'COLUMN') THEN
        RAISE_APPLICATION_ERROR(-20002, 'target_kind must be TABLE or COLUMN.');
    END IF;
    l_annotation_name := simple_name(p_annotation_name, 'annotation_name');
    IF p_change_text IS NULL OR LENGTH(p_change_text) = 0 THEN
        RAISE_APPLICATION_ERROR(-20003, 'change_text must not be empty.');
    END IF;
    IF LENGTH(p_change_text) > 4000 THEN
        RAISE_APPLICATION_ERROR(-20004, 'change_text must be 4000 characters or less.');
    END IF;

    BEGIN
        SELECT object_type
          INTO l_object_type
          FROM all_objects
         WHERE owner = l_schema_name
           AND object_name = l_object_name
           AND object_type IN ('TABLE', 'VIEW')
           AND ROWNUM = 1;
    EXCEPTION
        WHEN NO_DATA_FOUND THEN
            RAISE_APPLICATION_ERROR(-20005, 'TABLE or VIEW object was not found.');
    END;

    IF l_target_kind = 'COLUMN' THEN
        IF p_column_name IS NULL THEN
            RAISE_APPLICATION_ERROR(-20006, 'column_name is required for COLUMN target.');
        END IF;
        IF l_object_type = 'VIEW' THEN
            RAISE_APPLICATION_ERROR(-20007, 'VIEW column annotations cannot be altered by Oracle.');
        END IF;
        l_column_name := simple_name(p_column_name, 'column_name');
        BEGIN
            SELECT 1 INTO l_exists
              FROM all_tab_columns
             WHERE owner = l_schema_name
               AND table_name = l_object_name
               AND column_name = l_column_name
               AND ROWNUM = 1;
        EXCEPTION
            WHEN NO_DATA_FOUND THEN
                RAISE_APPLICATION_ERROR(-20008, 'column_name does not exist on the table.');
        END;
    ELSIF p_column_name IS NOT NULL THEN
        RAISE_APPLICATION_ERROR(-20009, 'column_name is not allowed for TABLE target.');
    END IF;

    SELECT COUNT(*)
      INTO l_exists
      FROM all_annotations_usage
     WHERE annotation_owner = l_schema_name
       AND object_name = l_object_name
       AND object_type = l_object_type
       AND annotation_name = l_annotation_name
       AND (l_target_kind = 'TABLE' AND column_name IS NULL
            OR l_target_kind = 'COLUMN' AND column_name = l_column_name);

    IF l_exists > 0 THEN
        IF l_target_kind = 'COLUMN' THEN
            l_sql := 'ALTER TABLE ' || qname(l_schema_name) || '.' || qname(l_object_name)
                  || ' MODIFY ' || qname(l_column_name) || ' ANNOTATIONS (DROP '
                  || qname(l_annotation_name) || ')';
        ELSIF l_object_type = 'VIEW' THEN
            l_sql := 'ALTER VIEW ' || qname(l_schema_name) || '.' || qname(l_object_name)
                  || ' ANNOTATIONS (DROP ' || qname(l_annotation_name) || ')';
        ELSE
            l_sql := 'ALTER TABLE ' || qname(l_schema_name) || '.' || qname(l_object_name)
                  || ' ANNOTATIONS (DROP ' || qname(l_annotation_name) || ')';
        END IF;
        EXECUTE IMMEDIATE l_sql;
        l_action := 'REPLACED';
    ELSE
        l_action := 'ADDED';
    END IF;

    IF l_target_kind = 'COLUMN' THEN
        l_sql := 'ALTER TABLE ' || qname(l_schema_name) || '.' || qname(l_object_name)
              || ' MODIFY ' || qname(l_column_name) || ' ANNOTATIONS (ADD '
              || qname(l_annotation_name) || ' ' || literal(p_change_text) || ')';
    ELSIF l_object_type = 'VIEW' THEN
        l_sql := 'ALTER VIEW ' || qname(l_schema_name) || '.' || qname(l_object_name)
              || ' ANNOTATIONS (ADD ' || qname(l_annotation_name) || ' '
              || literal(p_change_text) || ')';
    ELSE
        l_sql := 'ALTER TABLE ' || qname(l_schema_name) || '.' || qname(l_object_name)
              || ' ANNOTATIONS (ADD ' || qname(l_annotation_name) || ' '
              || literal(p_change_text) || ')';
    END IF;
    EXECUTE IMMEDIATE l_sql;

    RETURN l_action || ': ' || l_schema_name || '.' || l_object_name
        || CASE WHEN l_column_name IS NULL THEN '' ELSE '.' || l_column_name END
        || ' [' || l_annotation_name || ']';
EXCEPTION
    WHEN OTHERS THEN
        IF SQLCODE BETWEEN -20099 AND -20000 THEN
            RAISE;
        END IF;
        RAISE_APPLICATION_ERROR(-20099, 'annotation change failed: ' || SQLERRM);
END;
/

SHOW ERRORS FUNCTION sgmp_set_annotation;
