package client
import (
"context"
"fmt"
"io"
"net/http"
"regexp"
"strings"
"time"
"github.com/scottyah/paragliding/internal/model"
)
const (
faaTFRListURL = "https://tfr.faa.gov/tfr2/list.jsp"
faaBaseURL = "https://tfr.faa.gov"
)
// FAAClient fetches TFR data from the FAA website
type FAAClient struct {
httpClient *http.Client
}
// NewFAAClient creates a new FAA TFR client
func NewFAAClient() *FAAClient {
return &FAAClient{
httpClient: &http.Client{
Timeout: 15 * time.Second,
},
}
}
var (
anchorRe = regexp.MustCompile(`(?is)]*)>(.*?)`)
hrefRe = regexp.MustCompile(`(?i)href="([^"]*)"`)
dateRe = regexp.MustCompile(`^\d{2}/\d{2}/\d{4}$`)
htmlTagRe = regexp.MustCompile(`<[^>]*>`)
)
type anchor struct {
href string
text string
}
// FetchTFRs retrieves the list of active TFRs from the FAA website
func (c *FAAClient) FetchTFRs(ctx context.Context) ([]model.TFR, error) {
req, err := http.NewRequestWithContext(ctx, "GET", faaTFRListURL, nil)
if err != nil {
return nil, fmt.Errorf("create request: %w", err)
}
req.Header.Set("User-Agent", "ParaglidingWeatherApp/1.0")
resp, err := c.httpClient.Do(req)
if err != nil {
return nil, fmt.Errorf("fetch TFR list: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("TFR list returned status %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("read response: %w", err)
}
return parseTFRList(string(body)), nil
}
// extractAnchors finds all tags in HTML and returns their href and text content
func extractAnchors(html string) []anchor {
matches := anchorRe.FindAllStringSubmatch(html, -1)
anchors := make([]anchor, 0, len(matches))
for _, m := range matches {
text := htmlTagRe.ReplaceAllString(m[2], "")
text = strings.TrimSpace(text)
a := anchor{text: text}
if hrefMatch := hrefRe.FindStringSubmatch(m[1]); hrefMatch != nil {
a.href = hrefMatch[1]
}
anchors = append(anchors, a)
}
return anchors
}
// parseTFRList extracts TFR entries from the FAA list page HTML.
// The page contains a table where each row has anchor tags for:
// date, notam ID (with detail link), facility, state, type, description, zoom link
func parseTFRList(html string) []model.TFR {
rows := strings.Split(html, "|