package main

import (
    "bytes"
    "encoding/json"
    "errors"
    "io"
    "mime"
    "net/http"
    "sort"
    "strconv"
    "strings"
    "sync"
    "unicode"
    "unicode/utf8"
)

type Route struct { Name string; ID int; Allow string; Status int }
func MatchRoute(method,path string) Route {
    name,allow,id := "","",0
    switch {
    case path=="/healthz": name,allow="health","GET, HEAD"
    case path=="/notes": name,allow="collection","GET, POST"
    case strings.HasPrefix(path,"/notes/"):
        raw:=strings.TrimPrefix(path,"/notes/")
        parsed,err:=strconv.Atoi(raw)
        if err!=nil || parsed<1 || parsed>1000000 || strconv.Itoa(parsed)!=raw { return Route{Status:404} }
        name,allow,id="item","GET, DELETE",parsed
    default: return Route{Status:404}
    }
    for _,accepted:=range strings.Split(allow,", ") {
        if method==accepted { return Route{Name:name,ID:id,Allow:allow,Status:200} }
    }
    return Route{Name:name,ID:id,Allow:allow,Status:405}
}

const MaxBody = 1024
var ErrBodyTooLarge=errors.New("body too large")
var ErrInput=errors.New("invalid note input")
type CreateInput struct { Title string `json:"title"` }
func DecodeCreate(reader io.Reader) (CreateInput,error) {
    data,err:=io.ReadAll(io.LimitReader(reader,MaxBody+1))
    if err!=nil { return CreateInput{},ErrInput }
    if len(data)>MaxBody { return CreateInput{},ErrBodyTooLarge }
    return parseCreate(data)
}
// parseCreate supplies the strict single-object schema; the exercise bounds IO.
func parseCreate(data []byte) (CreateInput,error) {
    if !utf8.Valid(data) { return CreateInput{},ErrInput }
    decoder:=json.NewDecoder(bytes.NewReader(data))
    first,err:=decoder.Token()
    if err!=nil || first!=json.Delim('{') { return CreateInput{},ErrInput }
    input:=CreateInput{};seen:=false
    for decoder.More() {
        key,err:=decoder.Token()
        if err!=nil || key!="title" || seen { return CreateInput{},ErrInput }
        var value any
        if decoder.Decode(&value)!=nil { return CreateInput{},ErrInput }
        title,ok:=value.(string)
        if !ok { return CreateInput{},ErrInput }
        input.Title=strings.TrimSpace(title);seen=true
    }
    last,err:=decoder.Token()
    if err!=nil || last!=json.Delim('}') || !seen { return CreateInput{},ErrInput }
    var extra any
    if decoder.Decode(&extra)!=io.EOF { return CreateInput{},ErrInput }
    if n:=utf8.RuneCountInString(input.Title); n<1 || n>80 { return CreateInput{},ErrInput }
    for _,r:=range input.Title { if unicode.IsControl(r) { return CreateInput{},ErrInput } }
    return input,nil
}

type ErrorBody struct { Error string `json:"error"` }
func encodedJSON(value any)([]byte,error) {
    data,err:=json.Marshal(value)
    if err!=nil { return nil,err };return append(data,'\n'),nil
}
func bodyLength(data []byte)string { return strconv.Itoa(len(data)) }
func WriteJSON(w http.ResponseWriter,status int,value any,head bool) error {
    data,err:=encodedJSON(value)
    if err!=nil { return err }
    w.Header().Set("Content-Type","application/json; charset=utf-8")
    w.Header().Set("X-Content-Type-Options","nosniff")
    w.Header().Set("Content-Length",bodyLength(data))
    w.WriteHeader(status)
    if head { return nil }
    _,err=w.Write(data)
    return err
}

var ErrNotFound=errors.New("note not found")
var ErrFull=errors.New("note capacity reached")
type Note struct { ID int `json:"id"`; Title string `json:"title"` }
type Repository interface { Create(string)(Note,error); List()[]Note; Get(int)(Note,error); Delete(int)error }
type MemoryStore struct { mu sync.Mutex; next int; notes map[int]Note }
func NewMemoryStore()*MemoryStore { return &MemoryStore{next:1,notes:make(map[int]Note)} }
func (s *MemoryStore) Create(title string)(Note,error) {
    s.mu.Lock();defer s.mu.Unlock()
    if len(s.notes)>=100 || s.next>1000000 { return Note{},ErrFull }
    note:=Note{ID:s.next,Title:title};s.next++
    s.notes[note.ID]=note
    return note,nil
}
func (s *MemoryStore) List()[]Note {
    s.mu.Lock();defer s.mu.Unlock()
    result:=make([]Note,0,len(s.notes))
    for _,note:=range s.notes { result=append(result,note) }
    sort.Slice(result,func(i,j int)bool{return result[i].ID<result[j].ID})
    return result
}
func (s *MemoryStore) Get(id int)(Note,error) {
    s.mu.Lock();defer s.mu.Unlock()
    note,ok:=s.notes[id]
    if !ok { return Note{},ErrNotFound }
    return note,nil
}
func (s *MemoryStore) Delete(id int)error {
    s.mu.Lock();defer s.mu.Unlock()
    if _,ok:=s.notes[id];!ok { return ErrNotFound }
    delete(s.notes,id);return nil
}

func supportsJSON(raw string)bool { media,_,err:=mime.ParseMediaType(raw);return err==nil && media=="application/json" }
func NewHandler(repository Repository)http.Handler {
    panic("implement the stated contract")
}
