# GAIP agent-commerce record: validate and publish (the template a shop copies).
# Licence: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/), as every GAIP format is. Credit GAIP.
#
# Copy this file to .github/workflows/agent-commerce.yml in the repository that builds your shop's website, and keep
# your record at agent-commerce.json in the repository root (the generator at https://www.gaipagents.com/agent-commerce
# writes it). On every change the workflow checks the file against GAIP's published schema
# (https://www.gaipagents.com/agent-commerce/schema.json) with a small dependency-free script, then commits it to
# .well-known/agent-commerce.json, which your site serves at https://<your host>/.well-known/agent-commerce.json.
# GAIP reads that path weekly and on request; publishing it is your opt-in to GAIP's scheduled reads of your public
# product pages. Nothing is sent to GAIP by this workflow. Every value in the file is from a vocabulary, a number or an
# https URL; a file with free text is refused here and by GAIP.
name: Publish agent-commerce.json

on:
  push:
    paths:
      - agent-commerce.json
  workflow_dispatch:

permissions:
  contents: write

env:
  GAIP_RECORD: agent-commerce.json
  GAIP_SCHEMA_URL: https://www.gaipagents.com/agent-commerce/schema.json

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

      - name: Fetch GAIP's schema
        run: curl -fsSL "$GAIP_SCHEMA_URL" -o /tmp/gaip-agent-commerce-schema.json

      - name: Validate the record against the schema
        run: |
          python3 - "$GAIP_RECORD" /tmp/gaip-agent-commerce-schema.json <<'PY'
          """Check agent-commerce.json against GAIP's schema (gaip.agent-commerce.v1). Standard library only."""
          import json
          import re
          import sys

          record_path, schema_path = sys.argv[1], sys.argv[2]
          with open(record_path, "rb") as handle:
              raw = handle.read()
          with open(schema_path, encoding="utf-8") as handle:
              schema = json.load(handle)
          problems = []
          max_bytes = int((schema.get("x-gaip") or {}).get("max_bytes") or 65536)
          if len(raw) > max_bytes:
              problems.append(f"file is {len(raw)} bytes; at most {max_bytes}")
          try:
              record = json.loads(raw.decode("utf-8"))
          except (ValueError, UnicodeDecodeError) as exc:
              print(f"REFUSED: {record_path} is not UTF-8 JSON ({exc})")
              sys.exit(1)
          TYPES = {"object": dict, "array": list, "string": str, "integer": int, "number": (int, float),
                   "boolean": bool, "null": type(None)}


          def check(value, rule, path):
              types = rule.get("type")
              if types is not None:
                  allowed = types if isinstance(types, list) else [types]
                  ok = any(isinstance(value, TYPES[t]) and not (t in ("integer", "number") and isinstance(value, bool))
                           for t in allowed)
                  if not ok:
                      problems.append(f"{path}: expected {' or '.join(allowed)}, got {type(value).__name__}")
                      return
              if "const" in rule and value != rule["const"]:
                  problems.append(f"{path}: must be exactly {json.dumps(rule['const'])[:80]}")
              if "enum" in rule and value not in rule["enum"]:
                  problems.append(f"{path}: {json.dumps(value)[:60]} is not one of {rule['enum']} (free text is not accepted)")
              if isinstance(value, str):
                  if "pattern" in rule and not re.search(rule["pattern"], value):
                      problems.append(f"{path}: {json.dumps(value)[:60]} is not of the form {rule['pattern']}")
                  if "maxLength" in rule and len(value) > rule["maxLength"]:
                      problems.append(f"{path}: longer than {rule['maxLength']} characters")
                  if rule.get("format") == "uri" and not value.startswith("https://"):
                      problems.append(f"{path}: an https URL is required")
              if isinstance(value, (int, float)) and not isinstance(value, bool):
                  if "minimum" in rule and value < rule["minimum"]:
                      problems.append(f"{path}: below {rule['minimum']}")
                  if "maximum" in rule and value > rule["maximum"]:
                      problems.append(f"{path}: above {rule['maximum']}")
              if isinstance(value, dict):
                  for key in rule.get("required", []):
                      if key not in value:
                          problems.append(f"{path}: {key} is required")
                  props = rule.get("properties") or {}
                  for key, item in value.items():
                      if key in props:
                          check(item, props[key], f"{path}.{key}")
                      elif rule.get("additionalProperties") is False:
                          problems.append(f"{path}: {key} is not a field of the format (free text is not accepted)")
              if isinstance(value, list):
                  if "minItems" in rule and len(value) < rule["minItems"]:
                      problems.append(f"{path}: at least {rule['minItems']} item(s)")
                  if "maxItems" in rule and len(value) > rule["maxItems"]:
                      problems.append(f"{path}: at most {rule['maxItems']} items")
                  if rule.get("uniqueItems") and len({json.dumps(v, sort_keys=True) for v in value}) != len(value):
                      problems.append(f"{path}: repeated items")
                  if "items" in rule:
                      for index, item in enumerate(value):
                          check(item, rule["items"], f"{path}[{index}]")


          check(record, schema, "record")
          if problems:
              print(f"REFUSED: {record_path} is not valid against {schema.get('$id')}:")
              for problem in problems:
                  print(" -", problem)
              sys.exit(1)
          print(f"OK: {record_path} is valid against {schema.get('$id')} ({len(raw)} bytes).")
          PY

      - name: Publish to .well-known
        run: |
          mkdir -p .well-known
          cp "$GAIP_RECORD" .well-known/agent-commerce.json
          git config user.name "github-actions[bot]"
          git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
          git add .well-known/agent-commerce.json
          if git diff --cached --quiet; then
            echo "Already published: .well-known/agent-commerce.json is unchanged."
            exit 0
          fi
          git commit -m "Publish agent-commerce.json to .well-known (GAIP agent-commerce record, gaip.agent-commerce.v1)"
          git push
