#!/usr/bin/env perl

use strict;
use warnings;
use feature 'say';

use LWP::UserAgent;
use JSON::MaybeXS qw(decode_json);
use Mojo::DOM;

my $url = shift or die "Usage: $0 url";
my $ua  = LWP::UserAgent->new(timeout => 10);
my $res = $ua->get($url);
die "Failed to fetch '$url': " . $res->status_line unless $res->is_success();
my $html = $res->decoded_content;

# Parse once; reuse the same tree for JSON-LD extraction and HTML fallback.
my $dom = Mojo::DOM->new($html);

# ---------------------------------------------------------------------------
# Primary: extract event data from JSON-LD blocks
# Mojo::DOM handles CDATA sections, unusual whitespace and extra attributes
# that cause bare regex to fail silently.
# ---------------------------------------------------------------------------
my $event;
$dom->find('script[type="application/ld+json"]')->each(sub {
	return if $event;          # stop once found
	my $json = eval { decode_json($_[0]->all_text) };
	return unless $json;

	my @objs = ref $json eq 'ARRAY' ? @{$json} : ($json);
	for my $obj (@objs) {
		next unless ref $obj eq 'HASH' && $obj->{'@type'};
		# Accept Event and all subtypes (MusicEvent, etc.)
		if ($obj->{'@type'} =~ /Event$/i) {
			$event = $obj;
			last;
		}
	}
});

# Result fields; populated from JSON-LD first, HTML fallback where missing.
my %info = (
	title         => '',
	organizer     => '',
	organizer_url => '',
	venue         => '',
	address       => '',
	date          => '',
	time          => '',
	event_url     => '',
	description   => '',
);

if ($event) {
	$info{title}     = $event->{name}  // '';
	$info{event_url} = $event->{url}   // $url;

	# Organizer may be a single object or an array of objects.
	if (my $org = $event->{organizer}) {
		my $first = ref($org) eq 'ARRAY' ? $org->[0] : $org;
		$info{organizer}     = $first->{name} // '';
		$info{organizer_url} = $first->{url}  // '';
	}

	# Location / venue
	if (my $loc = $event->{location}) {
		$info{venue} = $loc->{name} // '';
		if (my $addr = $loc->{address}) {
			if (ref $addr) {
				# Compose address from structured PostalAddress fields.
				my @parts = map { $addr->{$_} // () }
					qw(streetAddress addressLocality addressRegion postalCode addressCountry);
				$info{address} = join ', ', @parts if @parts;
			} else {
				$info{address} = $addr;
			}
		}
	}

	# Split startDate into date and time components.
	if (my $start = $event->{startDate}) {
		if ($start =~ /^(\d{4}-\d{2}-\d{2})[T ](\d{2}:\d{2}(?::\d{2})?)/) {
			$info{date} = $1;
			$info{time} = $2;
		} else {
			$info{date} = $start;
		}
	}

	$info{description} = $event->{description} // '';
}

# ---------------------------------------------------------------------------
# Fallback: fill any missing fields from the rendered HTML using Mojo::DOM
# CSS selectors replace the HTML::TreeBuilder look_down() calls.
# ---------------------------------------------------------------------------

# Title fallback: first <h1>
if (!$info{title}) {
	if (my $h1 = $dom->at('h1')) {
		$info{title} = $h1->text;
	}
}

# Organizer fallback: a <div> whose text contains "Host Details"
if (!$info{organizer}) {
	my $host = $dom->find('div')
	               ->grep(sub { index($_->all_text, 'Host Details') >= 0 })
	               ->first;
	if ($host) {
		($info{organizer} = $host->all_text) =~ s/Host Details[:\s]*//i;
		$info{organizer} =~ s/\s+/ /g;
		$info{organizer} =~ s/^\s+|\s+$//g;
	}
}

# Date/time fallback: a <div> whose text contains "Date & Location"
if (!$info{date} || !$info{time}) {
	my $dt_div = $dom->find('div')
	                 ->grep(sub { index($_->all_text, 'Date & Location') >= 0 })
	                 ->first;
	if ($dt_div) {
		my $text = $dt_div->all_text;
		# Example: "Thu, 18 Dec, 2025 at 07:00 pm (EST)"
		if ($text =~ /(\w{3}),\s+(\d{1,2} \w{3}),\s+(\d{4}) at (\d{1,2}:\d{2}(?:\s*[ap]m)?)/) {
			$info{date} = "$2, $3";
			$info{time} = $4;
		} else {
			$info{date} ||= $text;
		}
	}
}

# Venue fallback: first <h2>
if (!$info{venue}) {
	if (my $h2 = $dom->at('h2')) {
		$info{venue} = $h2->text;
	}
}

# Description fallback: <div id="about">, else first <p>
if (!$info{description}) {
	if (my $about = $dom->at('div#about')) {
		$info{description} = $about->all_text;
	} elsif (my $para = $dom->at('p')) {
		$info{description} = $para->text;
	}
}

# ---------------------------------------------------------------------------
# Output
# ---------------------------------------------------------------------------
say "Title:\t\t$info{title}";
say "Organizer:\t$info{organizer}"     if $info{organizer};
say "Organizer URL:\t$info{organizer_url}" if $info{organizer_url};
say "Venue:\t\t$info{venue}";
say "Address:\t$info{address}";
say "Date:\t\t$info{date}";
say "Time:\t\t$info{time}"             if $info{time};
say "Event URL:\t$info{event_url}";
say "Description:\t$info{description}";
