-- Migration: Implement URT taxonomy with PostgreSQL ltree
-- Benefits:
--   1. Hierarchical queries (find all codes under a domain/category)
--   2. Ancestor/descendant lookups in O(1)
--   3. Pattern matching on paths (e.g., 'O.*' for all Offering codes)
--   4. Efficient GiST indexing for tree operations
--   5. Aggregations at any level of hierarchy

-- Enable ltree extension
CREATE EXTENSION IF NOT EXISTS ltree;

-- ============================================================================
-- NEW UNIFIED TAXONOMY TABLE
-- ============================================================================

CREATE TABLE IF NOT EXISTS pipeline.urt_taxonomy (
    id SERIAL PRIMARY KEY,

    -- ltree path: Domain.Category.Subcode (e.g., 'O.O1.O1_01')
    path ltree NOT NULL UNIQUE,

    -- Human-readable code (e.g., 'O1.01')
    code VARCHAR(10) NOT NULL UNIQUE,

    -- Node type for filtering
    node_type VARCHAR(20) NOT NULL CHECK (node_type IN ('domain', 'category', 'subcode')),

    -- Hierarchy level (1=domain, 2=category, 3=subcode)
    level INT GENERATED ALWAYS AS (nlevel(path)) STORED,

    -- Names and definitions
    name VARCHAR(100) NOT NULL,
    definition TEXT,

    -- Examples (for subcodes)
    positive_example TEXT,
    negative_example TEXT,

    -- Actionability (for subcodes)
    solution TEXT,
    solution_complexity VARCHAR(10) DEFAULT 'medium',
    marketing_angle TEXT,

    -- Owner routing
    default_owner VARCHAR(50),

    -- Metadata
    is_active BOOLEAN DEFAULT TRUE,
    created_at TIMESTAMP DEFAULT NOW(),
    updated_at TIMESTAMP DEFAULT NOW()
);

-- ============================================================================
-- INDEXES FOR LTREE OPERATIONS
-- ============================================================================

-- GiST index for ltree operations (ancestor, descendant, pattern matching)
CREATE INDEX idx_urt_taxonomy_path_gist ON pipeline.urt_taxonomy USING GIST (path);

-- B-tree index for exact path lookups and sorting
CREATE INDEX idx_urt_taxonomy_path_btree ON pipeline.urt_taxonomy USING BTREE (path);

-- Index for code lookups (most common operation)
CREATE INDEX idx_urt_taxonomy_code ON pipeline.urt_taxonomy (code);

-- Index for node type filtering
CREATE INDEX idx_urt_taxonomy_node_type ON pipeline.urt_taxonomy (node_type);

-- ============================================================================
-- MIGRATE EXISTING DATA
-- ============================================================================

-- Insert domains (level 1)
INSERT INTO pipeline.urt_taxonomy (path, code, node_type, name, definition, default_owner)
SELECT
    code::ltree as path,
    code,
    'domain',
    name,
    -- Domain definitions from spec
    CASE code
        WHEN 'O' THEN 'Does the core product/service deliver?'
        WHEN 'P' THEN 'How do personnel behave and perform?'
        WHEN 'J' THEN 'Is the process smooth and timely?'
        WHEN 'E' THEN 'Is the space functional and pleasant?'
        WHEN 'A' THEN 'Can everyone participate fully?'
        WHEN 'V' THEN 'Is the exchange fair and transparent?'
        WHEN 'R' THEN 'Is trust built and maintained?'
    END,
    CASE code
        WHEN 'O' THEN 'Product/Operations'
        WHEN 'P' THEN 'HR/Training'
        WHEN 'J' THEN 'Operations/Process'
        WHEN 'E' THEN 'Facilities/IT'
        WHEN 'A' THEN 'Compliance/Design'
        WHEN 'V' THEN 'Finance/Pricing'
        WHEN 'R' THEN 'Leadership/CX'
    END
FROM pipeline.urt_domains
ON CONFLICT (code) DO NOTHING;

-- Insert categories (level 2)
INSERT INTO pipeline.urt_taxonomy (path, code, node_type, name, definition)
SELECT
    (domain_code || '.' || code)::ltree as path,
    code,
    'category',
    name,
    NULL -- Categories don't have definitions in current schema
FROM pipeline.urt_categories
ON CONFLICT (code) DO NOTHING;

-- Insert subcodes (level 3)
INSERT INTO pipeline.urt_taxonomy (path, code, node_type, name, definition, positive_example, negative_example, solution, solution_complexity, marketing_angle)
SELECT
    (domain_code || '.' || category_code || '.' || replace(code, '.', '_'))::ltree as path,
    code,
    'subcode',
    name,
    definition,
    positive_example,
    negative_example,
    solution,
    solution_complexity,
    marketing_angle
FROM pipeline.urt_subcodes
ON CONFLICT (code) DO NOTHING;

-- ============================================================================
-- HELPER FUNCTIONS
-- ============================================================================

-- Get all ancestors of a code (e.g., O1.01 -> [O, O1])
CREATE OR REPLACE FUNCTION pipeline.urt_ancestors(p_code VARCHAR)
RETURNS TABLE(code VARCHAR, name VARCHAR, node_type VARCHAR, level INT) AS $$
BEGIN
    RETURN QUERY
    SELECT t.code, t.name, t.node_type, t.level
    FROM pipeline.urt_taxonomy t
    WHERE t.path @> (SELECT path FROM pipeline.urt_taxonomy WHERE code = p_code)
    AND t.code != p_code
    ORDER BY t.level;
END;
$$ LANGUAGE plpgsql;

-- Get all descendants of a code (e.g., O -> all O* codes)
CREATE OR REPLACE FUNCTION pipeline.urt_descendants(p_code VARCHAR)
RETURNS TABLE(code VARCHAR, name VARCHAR, node_type VARCHAR, level INT) AS $$
BEGIN
    RETURN QUERY
    SELECT t.code, t.name, t.node_type, t.level
    FROM pipeline.urt_taxonomy t
    WHERE t.path <@ (SELECT path FROM pipeline.urt_taxonomy WHERE code = p_code)
    AND t.code != p_code
    ORDER BY t.path;
END;
$$ LANGUAGE plpgsql;

-- Get siblings (same parent)
CREATE OR REPLACE FUNCTION pipeline.urt_siblings(p_code VARCHAR)
RETURNS TABLE(code VARCHAR, name VARCHAR, level INT) AS $$
DECLARE
    v_parent ltree;
BEGIN
    SELECT subpath(path, 0, nlevel(path) - 1) INTO v_parent
    FROM pipeline.urt_taxonomy WHERE code = p_code;

    RETURN QUERY
    SELECT t.code, t.name, t.level
    FROM pipeline.urt_taxonomy t
    WHERE subpath(t.path, 0, nlevel(t.path) - 1) = v_parent
    AND t.code != p_code
    ORDER BY t.path;
END;
$$ LANGUAGE plpgsql;

-- Get domain for any code
CREATE OR REPLACE FUNCTION pipeline.urt_domain(p_code VARCHAR)
RETURNS VARCHAR AS $$
    SELECT code FROM pipeline.urt_taxonomy
    WHERE path @> (SELECT path FROM pipeline.urt_taxonomy WHERE code = p_code)
    AND node_type = 'domain';
$$ LANGUAGE SQL;

-- Get category for a subcode
CREATE OR REPLACE FUNCTION pipeline.urt_category(p_code VARCHAR)
RETURNS VARCHAR AS $$
    SELECT code FROM pipeline.urt_taxonomy
    WHERE path @> (SELECT path FROM pipeline.urt_taxonomy WHERE code = p_code)
    AND node_type = 'category';
$$ LANGUAGE SQL;

-- ============================================================================
-- VIEW: FLATTENED TAXONOMY WITH HIERARCHY INFO
-- ============================================================================

CREATE OR REPLACE VIEW pipeline.v_urt_taxonomy AS
SELECT
    t.id,
    t.path,
    t.code,
    t.node_type,
    t.level,
    t.name,
    t.definition,
    -- Parent info
    CASE
        WHEN t.level > 1 THEN subpath(t.path, 0, t.level - 1)::text
        ELSE NULL
    END as parent_path,
    -- Domain info (for rollups)
    subpath(t.path, 0, 1)::text as domain_code,
    (SELECT name FROM pipeline.urt_taxonomy WHERE path = subpath(t.path, 0, 1)) as domain_name,
    -- Category info (for subcodes)
    CASE
        WHEN t.level >= 2 THEN subpath(t.path, 0, 2)::text
        ELSE NULL
    END as category_path,
    -- Full path as breadcrumb
    t.path::text as full_path,
    -- Actionability
    t.solution,
    t.default_owner,
    t.is_active
FROM pipeline.urt_taxonomy t
ORDER BY t.path;

-- ============================================================================
-- UPDATE REVIEW_SPANS TO USE LTREE
-- ============================================================================

-- Add ltree column to review_spans for efficient hierarchy queries
ALTER TABLE pipeline.review_spans
ADD COLUMN IF NOT EXISTS urt_path ltree;

-- Populate ltree paths from existing codes
UPDATE pipeline.review_spans rs
SET urt_path = t.path
FROM pipeline.urt_taxonomy t
WHERE rs.urt_primary = t.code
AND rs.urt_path IS NULL;

-- Create GiST index for hierarchy queries on spans
CREATE INDEX IF NOT EXISTS idx_review_spans_urt_path_gist
ON pipeline.review_spans USING GIST (urt_path);

-- ============================================================================
-- EXAMPLE QUERIES (for reference)
-- ============================================================================

-- These are example queries, not executed:
/*

-- 1. Find all subcodes under "People" domain
SELECT code, name FROM pipeline.urt_taxonomy
WHERE path <@ 'P' AND node_type = 'subcode';

-- 2. Find all codes matching pattern (e.g., all Value subcodes)
SELECT code, name FROM pipeline.urt_taxonomy
WHERE path ~ 'V.*' AND node_type = 'subcode';

-- 3. Aggregate span counts by domain
SELECT
    subpath(urt_path, 0, 1)::text as domain,
    COUNT(*) as span_count
FROM pipeline.review_spans
WHERE urt_path IS NOT NULL
GROUP BY subpath(urt_path, 0, 1)
ORDER BY span_count DESC;

-- 4. Aggregate by category within a domain
SELECT
    subpath(urt_path, 0, 2)::text as category,
    COUNT(*) as span_count
FROM pipeline.review_spans
WHERE urt_path <@ 'O'  -- All Offering codes
GROUP BY subpath(urt_path, 0, 2)
ORDER BY span_count DESC;

-- 5. Get ancestors of a specific code
SELECT * FROM pipeline.urt_ancestors('O1.01');
-- Returns: O (Offering), O1 (Function)

-- 6. Get all descendants of a category
SELECT * FROM pipeline.urt_descendants('O1');
-- Returns: O1.01, O1.02, O1.03, O1.04, O1.05

-- 7. Find the domain owner for a code
SELECT pipeline.urt_domain('P1.01');
-- Returns: P (People)

-- 8. Drill-down query: Domain -> Category -> Subcode
WITH RECURSIVE tree AS (
    SELECT path, code, name, level
    FROM pipeline.urt_taxonomy
    WHERE node_type = 'domain' AND code = 'O'

    UNION ALL

    SELECT t.path, t.code, t.name, t.level
    FROM pipeline.urt_taxonomy t
    JOIN tree ON t.path <@ tree.path AND nlevel(t.path) = nlevel(tree.path) + 1
)
SELECT * FROM tree ORDER BY path;

*/

-- ============================================================================
-- TRIGGER: Auto-update urt_path on review_spans
-- ============================================================================

CREATE OR REPLACE FUNCTION pipeline.set_urt_path()
RETURNS TRIGGER AS $$
BEGIN
    NEW.urt_path := (SELECT path FROM pipeline.urt_taxonomy WHERE code = NEW.urt_primary);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS trg_set_urt_path ON pipeline.review_spans;
CREATE TRIGGER trg_set_urt_path
    BEFORE INSERT OR UPDATE OF urt_primary ON pipeline.review_spans
    FOR EACH ROW
    EXECUTE FUNCTION pipeline.set_urt_path();

-- ============================================================================
-- MATERIALIZED VIEW: Pre-computed hierarchy rollups
-- ============================================================================

CREATE MATERIALIZED VIEW IF NOT EXISTS pipeline.mv_urt_domain_stats AS
SELECT
    subpath(rs.urt_path, 0, 1)::text as domain_code,
    t.name as domain_name,
    rs.valence,
    COUNT(*) as span_count,
    COUNT(DISTINCT rs.review_id) as review_count,
    AVG(CASE rs.intensity
        WHEN 'I1' THEN 1
        WHEN 'I2' THEN 2
        WHEN 'I3' THEN 3
    END) as avg_intensity
FROM pipeline.review_spans rs
JOIN pipeline.urt_taxonomy t ON subpath(rs.urt_path, 0, 1) = t.path
WHERE rs.urt_path IS NOT NULL
GROUP BY subpath(rs.urt_path, 0, 1), t.name, rs.valence;

CREATE UNIQUE INDEX ON pipeline.mv_urt_domain_stats (domain_code, valence);

-- Refresh command (run periodically):
-- REFRESH MATERIALIZED VIEW CONCURRENTLY pipeline.mv_urt_domain_stats;

COMMENT ON TABLE pipeline.urt_taxonomy IS 'Unified URT taxonomy using ltree for hierarchical queries. Replaces urt_domains, urt_categories, urt_subcodes.';
