# shellcheck shell=dash
# Schema validation for ondb — pure POSIX, no JSON/YAML parsers needed.
# Schema file: $dir/schema.tsv (flat TSV, AWK-parseable)
#
# Format:
#   type\t<TypeName>
#   req\t<TypeName>\t<prop1,prop2,...>
#   forbid\t<TypeName>\t<prop1,prop2,...>
#   enum\t<TypeName>\t<prop>\t<val1,val2,...>
#   rel\t<RelName>
#   rfrom\t<RelName>\t<Type1,Type2,...>
#   rto\t<RelName>\t<Type1,Type2,...>
#   rcard\t<RelName>\t<one_to_one|one_to_many|many_to_one|many_to_many>
#   racyclic\t<RelName>\t<true>

___x_cmd_ondb_schema_has(){
    local dir="${1:-.}"
    [ -f "$dir/schema.tsv" ] && { x_="1"; return; }
    x_=""
}

# Validate add: type exists, required present, forbidden absent, enum valid.
# Args: schema_file type [prop1=val1 ...]
___x_cmd_ondb_schema_check_add(){
    local sf="$1" type="$2"
    shift 2
    [ -f "$sf" ] || return 0

    local out rc
    out="$( printf '%s\n' "$@" | awk -F'\t' -v type="$type" -v sf="$sf" '
    BEGIN {
        while ((getline line < sf) > 0) {
            n = split(line, a, "\t")
            if (n < 2) continue
            if (a[1] == "type" && a[2] == type) has_type = 1
            else if (a[1] == "req" && a[2] == type && n >= 3) {
                m = split(a[3], b, ",")
                for (i=1; i<=m; i++) if (b[i] != "") req[b[i]] = 1
            }
            else if (a[1] == "forbid" && a[2] == type && n >= 3) {
                m = split(a[3], b, ",")
                for (i=1; i<=m; i++) if (b[i] != "") forbid[b[i]] = 1
            }
            else if (a[1] == "enum" && a[2] == type && n >= 4) {
                m = split(a[4], b, ",")
                for (i=1; i<=m; i++) if (b[i] != "") enum_val[a[3], b[i]] = 1
                enum_prop[a[3]] = 1
            }
        }
        close(sf)
    }
    {
        if (index($0, "=") > 0) {
            k = substr($0, 1, index($0, "=") - 1)
            v = substr($0, index($0, "=") + 1)
            props[k] = v
        }
    }
    END {
        if (!has_type) { print "ERR\tUnknown type: " type; exit 1 }
        err = 0
        for (r in req) if (!(r in props)) { print "ERR\tMissing required property: " r; err = 1 }
        for (k in props) {
            if (k in forbid) { print "ERR\tForbidden property: " k; err = 1 }
            if (k in enum_prop && !((k, props[k]) in enum_val)) { print "ERR\tInvalid value for " k ": " props[k]; err = 1 }
        }
        exit err
    }
    ' )"
    rc=$?
    if [ "$rc" -ne 0 ]; then
        printf '%s\n' "$out" >&2
        N=ondb M="Schema validation failed" log:ret:64
        return 64
    fi
}

# Validate set: enum valid, forbidden absent.
# Args: schema_file entity_type [prop1=val1 ...]
___x_cmd_ondb_schema_check_set(){
    local sf="$1" etype="$2"
    shift 2
    [ -f "$sf" ] || return 0
    [ -n "$etype" ] || return 0

    local out rc
    out="$( printf '%s\n' "$@" | awk -F'\t' -v type="$etype" -v sf="$sf" '
    BEGIN {
        while ((getline line < sf) > 0) {
            n = split(line, a, "\t")
            if (n < 2) continue
            if (a[1] == "forbid" && a[2] == type && n >= 3) {
                m = split(a[3], b, ",")
                for (i=1; i<=m; i++) if (b[i] != "") forbid[b[i]] = 1
            }
            else if (a[1] == "enum" && a[2] == type && n >= 4) {
                m = split(a[4], b, ",")
                for (i=1; i<=m; i++) if (b[i] != "") enum_val[a[3], b[i]] = 1
                enum_prop[a[3]] = 1
            }
        }
        close(sf)
    }
    {
        if (index($0, "=") > 0) {
            k = substr($0, 1, index($0, "=") - 1)
            v = substr($0, index($0, "=") + 1)
            props[k] = v
        }
    }
    END {
        err = 0
        for (k in props) {
            if (k in forbid) { print "ERR\tForbidden property: " k; err = 1 }
            if (k in enum_prop && !((k, props[k]) in enum_val)) { print "ERR\tInvalid value for " k ": " props[k]; err = 1 }
        }
        exit err
    }
    ' )"
    rc=$?
    if [ "$rc" -ne 0 ]; then
        printf '%s\n' "$out" >&2
        N=ondb M="Schema validation failed" log:ret:64
        return 64
    fi
}

# Validate link: relation exists; from/to types match; cardinality not violated.
# Args: schema_file from_id rel to_id [input_cmd_for_reading_graph]
___x_cmd_ondb_schema_check_link(){
    local sf="$1" from="$2" rel="$3" to="$4"
    local input="${5:-}"
    [ -f "$sf" ] || return 0

    # 1) relation must exist in schema
    local has_rel
    has_rel="$( awk -F'\t' -v r="$rel" -v sf="$sf" '
        BEGIN { while ((getline line < sf) > 0) { n=split(line,a,"\t"); if (a[1]=="rel" && a[2]==r) found=1 } close(sf); print found+0 }
    ' )"
    if [ "$has_rel" != "1" ]; then
        N=ondb M="Schema validation failed: unknown relation '$rel'" log:ret:64
        return 64
    fi

    # 2) If we have graph input, check from/to entity types and cardinality
    [ -n "$input" ] || return 0

    # Get entity types and current links via AWK
    local from_type to_type
    from_type="$( eval "$input" | ___x_cmd_ondb_cawk get -v ONTO_ID="$from" -v ONTO_FMT=tsv 2>/dev/null | awk -F'\t' '$1=="type"{print $2}' )"
    to_type="$(   eval "$input" | ___x_cmd_ondb_cawk get -v ONTO_ID="$to"   -v ONTO_FMT=tsv 2>/dev/null | awk -F'\t' '$1=="type"{print $2}' )"

    if [ -z "$from_type" ]; then
        N=ondb M="Schema validation failed: from entity '$from' not found" log:ret:64
        return 64
    fi
    if [ -z "$to_type" ]; then
        N=ondb M="Schema validation failed: to entity '$to' not found" log:ret:64
        return 64
    fi

    # Check type constraints
    local allowed_from allowed_to
    allowed_from="$( awk -F'\t' -v r="$rel" -v sf="$sf" '
        BEGIN { while ((getline line < sf) > 0) { n=split(line,a,"\t"); if (a[1]=="rfrom" && a[2]==r) print a[3] } close(sf) }
    ' )"
    allowed_to="$( awk -F'\t' -v r="$rel" -v sf="$sf" '
        BEGIN { while ((getline line < sf) > 0) { n=split(line,a,"\t"); if (a[1]=="rto" && a[2]==r) print a[3] } close(sf) }
    ' )"

    if [ -n "$allowed_from" ]; then
        local ok=0 t
        for t in $( printf '%s' "$allowed_from" | tr ',' ' ' ); do
            [ "$t" = "$from_type" ] && { ok=1; break; }
        done
        if [ "$ok" != "1" ]; then
            N=ondb M="Schema validation failed: relation '$rel' does not allow from type '$from_type'" log:ret:64
            return 64
        fi
    fi

    if [ -n "$allowed_to" ]; then
        local ok=0 t
        for t in $( printf '%s' "$allowed_to" | tr ',' ' ' ); do
            [ "$t" = "$to_type" ] && { ok=1; break; }
        done
        if [ "$ok" != "1" ]; then
            N=ondb M="Schema validation failed: relation '$rel' does not allow to type '$to_type'" log:ret:64
            return 64
        fi
    fi

    # Cardinality check
    local card
    card="$( awk -F'\t' -v r="$rel" -v sf="$sf" '
        BEGIN { while ((getline line < sf) > 0) { n=split(line,a,"\t"); if (a[1]=="rcard" && a[2]==r) print a[3] } close(sf) }
    ' )"
    [ -n "$card" ] || card="many_to_many"

    case "$card" in
        one_to_one)
            local from_cnt to_cnt
            from_cnt="$( eval "$input" | ___x_cmd_ondb_cawk linked -v ONTO_ID="$from" -v ONTO_REL="$rel" -v ONTO_DIR=outgoing -v ONTO_FMT=tsv 2>/dev/null | grep -c '.' )"
            to_cnt="$(   eval "$input" | ___x_cmd_ondb_cawk linked -v ONTO_ID="$to"   -v ONTO_REL="$rel" -v ONTO_DIR=incoming -v ONTO_FMT=tsv 2>/dev/null | grep -c '.' )"
            if [ "$from_cnt" != "0" ]; then
                N=ondb M="Schema validation failed: cardinality one_to_one violated for from='$from'" log:ret:64
                return 64
            fi
            if [ "$to_cnt" != "0" ]; then
                N=ondb M="Schema validation failed: cardinality one_to_one violated for to='$to'" log:ret:64
                return 64
            fi
            ;;
        many_to_one)
            local from_cnt
            from_cnt="$( eval "$input" | ___x_cmd_ondb_cawk linked -v ONTO_ID="$from" -v ONTO_REL="$rel" -v ONTO_DIR=outgoing -v ONTO_FMT=tsv 2>/dev/null | grep -c '.' )"
            if [ "$from_cnt" != "0" ]; then
                N=ondb M="Schema validation failed: cardinality many_to_one violated for from='$from'" log:ret:64
                return 64
            fi
            ;;
        one_to_many)
            local to_cnt
            to_cnt="$( eval "$input" | ___x_cmd_ondb_cawk linked -v ONTO_ID="$to" -v ONTO_REL="$rel" -v ONTO_DIR=incoming -v ONTO_FMT=tsv 2>/dev/null | grep -c '.' )"
            if [ "$to_cnt" != "0" ]; then
                N=ondb M="Schema validation failed: cardinality one_to_many violated for to='$to'" log:ret:64
                return 64
            fi
            ;;
        many_to_many|*) ;;  # no constraint
    esac
}
